Должны ли мы проверять аргументы метода в JavaScript API?
Я разрабатываю библиотеку JavaScript, которая будет использоваться сторонними разработчиками.
API включает методы с этой сигнатурой:
function doSomething (arg1, arg2, options)
- arg1, arg2 являются обязательными аргументами простого типа.
- options - это хэш-объект, содержащий необязательные аргументы.
Вы рекомендуете проверить, что:
- допустимы типы аргументов?
- Атрибуты параметров верны? Например: что разработчик не прошел по ошибке на Succes вместо onSuccess?
- Почему популярные библиотеки, такие как prototype.js, не проверяются?
Ответы
Ответ 1
Спасибо за подробные ответы.
Ниже мое решение - объект утилиты для валидаций, который можно легко расширить, чтобы в принципе подтвердить что-либо...
Код еще достаточно короткий, поэтому мне не нужно разбирать его в процессе производства.
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'})]);
Ответ 2
У вас есть право решать, нужно ли "защищать" от "контрактного" API. Во многих случаях чтение руководства библиотеки может дать пользователю понять, что он должен предоставлять аргументы того или иного типа, которые подчиняются этим и этим ограничениям.
Если вы намереваетесь сделать очень интуитивно понятный, удобный для пользователя API, было бы неплохо проверить ваши аргументы, по крайней мере, в режиме отладки. Однако валидация требует времени (и исходного кода = > пробела), поэтому также может быть приятно оставить его.
Это вам.
Ответ 3
Подтвердите как можно больше и распечатайте полезные сообщения об ошибках, которые помогут людям быстро и легко выявлять проблемы.
Введите этот код проверки с некоторыми специальными комментариями (например, //+++VALIDATE
и //--VALIDATE
), чтобы вы могли легко удалить его с помощью инструмента для высокоскоростной и сжатой производственной версии.
Ответ 4
Мы должны обнаружить и устранить проблемы как можно скорее. Если вы не используете 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 });
}
Ответ 5
Это зависит. Насколько велика эта библиотека? Говорят, что типизированные языки лучше подходят для крупных проектов со сложным API. Поскольку JS в некоторой степени гибрид, вы можете выбрать.
О проверке - мне не нравится защитное программирование, пользователь функции должен передавать действительные аргументы. И в JS размер кода имеет значение.
Ответ 6
Когда я разработал API, подобные этим в прошлом, я подтвердил все, что, по моему мнению, является "основным" требованием - в вашем примере я бы проверил первые два аргумента.
Пока вы указываете разумные значения по умолчанию, для вашего пользователя должно быть довольно просто определить, что "необязательные" аргументы не указаны правильно, так как они не будут вносить никаких изменений в приложение, но все будет работать должным образом.
Если API сложный, я бы посоветовал воспользоваться советом Aaron - добавьте комментарии, которые могут быть проанализированы компрессором вокруг вашей проверки, чтобы разработчики получили преимущество проверки, но могут извлечь лишний вес при нажатии кода в производство.
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.')
}
Ответ 7
Промежуточный способ заключается в возврате разумного значения по умолчанию (например, null), когда отсутствуют необходимые аргументы. Таким образом, код пользователя будет терпеть неудачу, а не ваш. И, вероятно, им будет легче выяснить, в чем проблема в коде, а не в вашем.
Ответ 8
Не проверять. Дополнительный код - это больше кода, который пользователь должен загружать, поэтому это очень реальная стоимость для пользователей и производственных систем. Ошибки аргументов достаточно легки, чтобы поймать разработчик; не обременяйте пользователя такими.