Должны ли мы проверять аргументы метода в JavaScript API? - PullRequest
16 голосов
/ 12 ноября 2009

Я занимаюсь разработкой библиотеки JavaScript, которая будет использоваться сторонними разработчиками. API включает методы с этой подписью:

функция doSomething (arg1, arg2, options)

  • arg1, arg2 - обязательные аргументы простого типа.
  • options - это хеш-объект, содержащий необязательные аргументы.

Вы бы порекомендовали проверить, что: - типы аргументов действительны? - параметры атрибутов верны? Например: чтобы разработчик не прошел по ошибке onSucces вместо onSuccess?

  • почему популярные библиотеки, такие как prototype.js, не проверяются?

Ответы [ 8 ]

11 голосов
/ 12 ноября 2009

Вы имеете право решать, делать ли «оборонительный» или «договорный» API. Во многих случаях чтение руководства библиотеки может дать понять ее пользователю, что он должен предоставить аргументы того или иного типа, которые подчиняются тем или иным ограничениям.

Если вы намереваетесь создать очень интуитивный, удобный для пользователя API, было бы неплохо проверить ваши аргументы, по крайней мере, в режиме отладки. Однако валидация стоит времени (и исходного кода => пробела), поэтому может быть неплохо не указывать ее.

Вам решать.

8 голосов
/ 12 ноября 2009

Проверяйте как можно больше и печатайте полезные сообщения об ошибках, которые помогают людям быстро и легко отследить проблемы.

Укажите этот код проверки с некоторыми специальными комментариями (например, //+++VALIDATE и //--VALIDATE), чтобы вы могли легко удалить его с помощью инструмента для высокоскоростной сжатой рабочей версии.

4 голосов
/ 15 ноября 2009

Спасибо за подробные ответы.

Ниже мое решение - служебный объект для проверок, который может быть легко расширен для проверки практически всего ... Код все еще достаточно короткий, так что мне не нужно разбирать его в работе.

WL.Validators = {

/*
 * Validates each argument in the array with the matching validator.
 * @Param array - a JavaScript array.
 * @Param validators - an array of validators - a validator can be a function or 
 *                     a simple JavaScript type (string).
 */
validateArray : function (array, validators){
    if (! WL.Utils.isDevelopmentMode()){
        return;
    }
    for (var i = 0; i < array.length; ++i ){            
        WL.Validators.validateArgument(array[i], validators[i]);
    }
},

/*
 * Validates a single argument.
 * @Param arg - an argument of any type.
 * @Param validator - a function or a simple JavaScript type (string).
 */
validateArgument : function (arg, validator){
    switch (typeof validator){
        // Case validation function.
        case 'function':
            validator.call(this, arg);
            break;              
        // Case direct type. 
        case 'string':
            if (typeof arg !== validator){
                throw new Error("Invalid argument '" + Object.toJSON(arg) + "' expected type " + validator);
            }
            break;
    }           
}, 

/*
 * Validates that each option attribute in the given options has a valid name and type.
 * @Param options - the options to validate.
 * @Param validOptions - the valid options hash with their validators:
 * validOptions = {
 *     onSuccess : 'function',
 *     timeout : function(value){...}
 * }
 */
validateOptions : function (validOptions, options){
    if (! WL.Utils.isDevelopmentMode() || typeof options === 'undefined'){
        return;
    }
    for (var att in options){
        if (! validOptions[att]){
            throw new Error("Invalid options attribute '" + att + "', valid attributes: " + Object.toJSON(validOptions));
        }
        try {
            WL.Validators.validateArgument(options[att], validOptions[att]);
        }
        catch (e){
            throw new Error("Invalid options attribute '" + att + "'");
        }
    }   
},

};

Вот несколько примеров того, как я его использую:

isUserAuthenticated : function(realm) {
WL.Validators.validateArgument(realm, 'string');



getLocation: function(options) {            
    WL.Validators.validateOptions{
        onSuccess: 'function', 
        onFailure: 'function'}, options);


makeRequest : function(url, options) {
    WL.Validators.validateArray(arguments, ['string', 
        WL.Validators.validateOptions.carry({
        onSuccess : 'function', 
        onFailure : 'function',
        timeout   : 'number'})]);
1 голос
/ 14 января 2016

Мы должны обнаружить и устранить проблемы как можно скорее. Если вы не используете TypeScript или Flow, вы, скорее, делаете это с проверочной библиотекой. Это поможет вам не тратить часы на поиск неясных ошибок, вызванных неверными типами, указанными в качестве аргументов. Похоже, многие воспринимают это серьезно - https://www.npmjs.com/package/aproba получает 9 миллионов (!) Загрузок в неделю.

Мне это не подходит, объяснил здесь http://dsheiko.com/weblog/validating-arguments-in-javascript-like-a-boss Я использую https://www.npmjs.com/package/bycontract, основанный на выражениях JSDoc:

import { validate } from "bycontract";

const PdfOptionsType = {
  scale: "?number"
}

function pdf( path, w, h, options, callback ) {
  validate( arguments, [
    "string",
    "!number",
    "!number",
    PdfOptionsType,
    "function=" ] );
  //...
  return validate( returnValue, "Promise" );
}

pdf( "/tmp/test.pdf", 1, 1, { scale: 1 } ); // ok
pdf( "/tmp/test.pdf", "1", 1, { scale: 1 } ); // ByContractError: Argument #1: expected non-nullable but got string

В методах вы можете просто повторно использовать существующий блок комментариев JSDoc:

import { validateJsdoc, typedef } from "bycontract";

typedef("#PdfOptionsType", {
  scale: "number"
});

class Page {
  @validateJsdoc(`
    @param {string}          path
    @param {!number}         w
    @param {!number}         h
    @param {#PdfOptionsType} options
    @param {function=}       callback
    @returns {Promise}
  `)
  pdf( path, w, h, options, callback ) {
    return Promise.resolve();
  }
}

Однако я сохраняю эту проверку в средах разработки и тестирования, но пропускаю ее в реальном времени:

import { config } from "bycontract";
if ( process.env.NODE_ENV === "production" ) {
  config({ enable: false });
}
0 голосов
/ 12 ноября 2009

Промежуточным способом было бы возвращение разумного значения по умолчанию (например, ноль), если требуемые аргументы отсутствуют. Таким образом, код пользователя потерпит неудачу, а не ваш. И им, вероятно, будет легче выяснить, в чем проблема в их коде, а не в вашем.

0 голосов
/ 12 ноября 2009

Когда я в прошлом разрабатывал подобные API-интерфейсы, я проверял все, что, на мой взгляд, является «основным» требованием - в вашем примере я бы проверил первые два аргумента.

Пока вы указываете разумные значения по умолчанию, для вашего пользователя должно быть довольно просто определить, что "необязательные" аргументы не указаны правильно, так как это не будет вносить никаких изменений в приложение, но все будет работать правильно .

Если API сложный, я бы посоветовал последовать совету Аарона - добавить комментарии, которые могут быть проанализированы компрессором вокруг вашей проверки, чтобы разработчики получили преимущество проверки, но могли извлечь дополнительный мертвый груз при вставке кода в производство.

EDIT:

Вот несколько примеров того, что мне нравится делать в тех случаях, когда необходима проверка. Этот частный случай довольно прост; Я, вероятно, не стал бы беспокоиться о валидации, поскольку это действительно тривиально. В зависимости от ваших потребностей, иногда попытка форсировать типы будет лучше проверки, как показано с целочисленным значением.

Предположим, что extend () - это функция, которая объединяет объекты и существуют вспомогательные функции:

    var f = function(args){
      args = extend({
        foo: 1,
        bar: function(){},
        biz: 'hello'
      }, args || {});

      // ensure foo is an int.
      args.foo = parseInt(args.foo);

      //<validation>
      if(!isNumeric(args.foo) || args.foo > 10 || args.foo < 0){
        throw new Error('foo must be a number between 0 and 10');
      }

      if(!isFunction(args.bar)){
        throw new Error('bar must be a valid function');
      }

      if(!isString(args.biz) || args.biz.length == 0){
        throw new Error('biz must be a string, and cannot be empty');
      }
      //</validation>
    };

РЕДАКТИРОВАТЬ 2:

Если вы хотите избежать распространенных орфографических ошибок, вы можете либо 1) принять и переназначить их, либо 2) проверить количество аргументов. Вариант 1 прост, вариант 2 может быть выполнен следующим образом, хотя я бы определенно реорганизовал его в собственный метод, например, Object.extendStrict () (пример кода работает с прототипом):

var args = {
  ar: ''
};
var base = {
  foo: 1,
  bar: function(){},
  biz: 'hello'
};
// save the original length
var length = Object.keys(base).length;
// extend
args = Object.extend(base, args || {});
// detect if there're any extras
if(Object.keys(args).length != length){
  throw new Error('Invalid argument specified. Please check the options.')
}
0 голосов
/ 12 ноября 2009

Это зависит. Насколько большой будет эта библиотека? Говорят, что типизированные языки лучше подходят для больших проектов со сложным API. Поскольку JS в некоторой степени гибрид, вы можете выбрать.

О проверке - мне не нравится защитное программирование, пользователь функции обязан передавать действительные аргументы. И в JS размер кода имеет значение.

0 голосов
/ 12 ноября 2009

Не подтверждать. Больше кода - это больше кода, который пользователь должен загрузить, так что это очень реальная стоимость для пользователя и производственных систем. Ошибки аргумента достаточно легко поддаются обнаружению разработчиком; не обременяйте пользователя такими.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...