2009-11-12 11 views
15

Estoy desarrollando una biblioteca de JavaScript que será utilizada por desarrolladores de terceros. La API incluye métodos con esta firma:¿Deberíamos validar los argumentos del método en la API de JavaScript?

función doSomething (arg1, arg2, opciones)

  • arg1, arg2 son 'requeridas' argumentos de tipo simple.
  • opciones es un objeto hash que contiene argumentos opcionales.

¿Recomendaría validar que: - los tipos de argumentos son válidos? - los atributos de opciones son correctos? Por ejemplo: ¿que el desarrollador no pasó por error en Succes en lugar de onSuccess?

  • ¿Por qué las bibliotecas populares como prototype.js no validan?

Respuesta

4

Gracias por las respuestas detalladas.

A continuación se muestra mi solución: un objeto de utilidad para validaciones que pueden ampliarse fácilmente para validar básicamente cualquier cosa ... El código sigue siendo suficientemente corto para que no tenga que analizarlo en producción.

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 + "'"); 
     } 
    } 
}, 

};

Heres unos pocos ejemplos de cómo lo uso:

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'})]); 
+0

Esa es una gran idea. Además de eso, incluso podría 'decorar' una función para validar sus argumentos, y dejar la función original 'limpia'. – xtofl

-6

No validar. Más código es más código que el usuario tiene que descargar, por lo que es un costo muy real para el usuario y los sistemas de producción. Los errores de argumento son fáciles de atrapar por el desarrollador; no cargue al usuario con tal.

+0

estoy de acuerdo que este enfoque es siempre mejor Si la API es lo suficientemente compleja, ninguna validación puede generar errores molestos. Por ejemplo, ha habido muchas veces al usar la biblioteca ExtJS que me hubiera gustado haber hecho más validación de los parámetros entrantes, ahorrándome horas de dolor de cabeza. – jvenema

+1

¿Qué preferirías tener? ¿Horas de dolor de cabeza, o no usuarios porque su sitio tarda demasiado en cargarse? La elección es obvia. –

+0

Tener ninguno es una opción aquí. Un poco más de trabajo por adelantado para que su compresor y/o proceso de construcción sea un poco "inteligente" significa que no habrá dolor de cabeza en el camino, y buenos tiempos de carga rápidos para sus usuarios. – jvenema

9

Tiene derecho a decidir si desea hacer una API "defensiva" frente a una API "contractual". En muchos casos, leer el manual de una biblioteca puede aclararle a su usuario que debe proporcionar argumentos de este o aquel tipo que obedezcan estas y esas restricciones.

Si pretende hacer una API muy intuitiva y fácil de usar, sería bueno validar sus argumentos, al menos en modo de depuración. Sin embargo, la validación cuesta tiempo (y el código fuente => espacio), por lo que también puede ser bueno dejarlo fuera.

Depende de usted.

0

Depende. ¿Qué tan grande sería esta biblioteca? Se dice que los idiomas mecanografiados son mejores para grandes proyectos con API compleja. Como JS es hasta cierto punto híbrido, puede elegir.

Sobre la validación - No me gusta la programación defensiva, el usuario de la función estará obligado a pasar argumentos válidos. Y en el tamaño de JS importa el código.

7

Valida todo lo que puedas e imprime mensajes de error útiles que ayudan a las personas a localizar problemas de forma rápida y fácil.

Cita este código de validación con algunos comentarios especiales (como //+++VALIDATE y //--VALIDATE) para que pueda eliminarlo fácilmente con una herramienta para una versión de producción comprimida de alta velocidad.

+3

De alta velocidad a la falla en la producción? –

+2

En el código de producción, la falla temprana ya no es tan importante ya que la mayoría de los errores han sido reparados. Dicho esto, si utiliza mi enfoque, puede crear fácilmente una versión más lenta y validadora e implementarla en producción si es necesario. –

0

Cuando desarrollé API como estas en el pasado, he validado todo lo que considero un requisito "importante": en su ejemplo, verificaría los dos primeros argumentos.

Siempre que especifique los valores predeterminados razonables, debería ser bastante simple para su usuario determinar que los argumentos "opcionales" no se especifican correctamente, ya que no hará ningún cambio en la aplicación, pero todo funcionará correctamente.

Si la API es compleja, sugiero seguir los consejos de Aaron: agregue comentarios que puedan ser analizados por un compresor para que los desarrolladores obtengan el beneficio de la validación, pero puedan extraer el peso muerto adicional al presionar el código en producción

EDIT:

Acá algunos ejemplos de lo que me gusta hacer en los casos en que es necesaria la validación. Este caso particular es bastante simple; Probablemente no me molestaría con la validación, ya que es realmente trivial. Dependiendo de sus necesidades, a veces intentar forzar tipos sería mejor que la validación, como se demuestra con el valor entero.

Supongamos extender() es una función que combina objetos, y existen las funciones de ayuda:

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> 
    }; 

EDIT 2:

Si se quiere evitar errores ortográficos comunes, puede 1) aceptar y reasignarlos o 2) validar el recuento de argumentos. La opción 1 es fácil, opción 2 podría ser hecho como éste, aunque sin duda lo refactorizar en su propio método, algo así como Object.extendStrict() (código de ejemplo funciona w/prototipo):

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

Los argumentos opcionales en algunos casos son más difíciles de detectar, por ejemplo, noté que si el valor del tiempo de espera era una cadena '60' - no se estableció el tiempo de espera (el valor pasó a prototype.js ..). ¿Siempre es posible validar los atributos opcionales en absoluto? Me di cuenta de que prototype.js a veces agrega métodos adicionales, entonces ¿cómo se pueden diferenciar los métodos mal nombrados que fueron establecidos por el desarrollador? – Totach

+0

Absolutamente, definitivamente pueden ser más difíciles de detectar; más razón para validar. He actualizado mi respuesta para dar algunos ejemplos. – jvenema

0

Un intermedio forma sería devolver un valor predeterminado razonable (por ejemplo, nulo) cuando faltan los argumentos necesarios. De esta forma, el código del usuario fallará, no el tuyo. Y probablemente sea más fácil para ellos descubrir cuál es el problema en su código en lugar de en el suyo.

1

Puede validar los dos puntos de entrada (argumentos) y salida (de retorno) por contratos. Aquí, un lib https://www.npmjs.com/package/bycontract que aceptan la sintaxis jsdoc para un contrato:

/** 
* @param {number|string} sum 
* @param {Object.<string, string>} payload 
* @param {function} cb 
* @returns {HTMLElement} 
*/ 
function foo(sum, payload, cb) { 
    // Test if the contract is respected at entry point 
    byContract(arguments, [ "number|string", "Object.<string, string>", "function" ]); 
    // .. 
    var res = document.createElement("div"); 
    // Test if the contract is respected at exit point 
    return byContract(res, "HTMLElement"); 
} 
// Test it 
foo(100, { foo: "foo" }, function(){}); // ok 
foo(100, { foo: 100 }, function(){}); // exception 

Sin embargo sigo esta validación en entornos dev/prueba/estadificación, pero evita en vivo:

if (env === "production") { 
    byContract.isEnabled = false; 
} 
Cuestiones relacionadas