Objetos de argumentos múltiples versus opciones

157

Al crear una función de JavaScript con múltiples argumentos, siempre me enfrento a esta opción: pasar una lista de argumentos frente a pasar un objeto de opciones.

Por ejemplo, estoy escribiendo una función para asignar una lista de nodos a una matriz:

function map(nodeList, callback, thisObject, fromIndex, toIndex){
    ...
}

En cambio, podría usar esto:

function map(options){
    ...
}

donde options es un objeto:

options={
    nodeList:...,
    callback:...,
    thisObject:...,
    fromIndex:...,
    toIndex:...
}

¿Cuál es la forma recomendada? ¿Existen pautas sobre cuándo usar uno versus el otro?

[Actualización] Parece haber un consenso a favor del objeto de opciones, por lo que me gustaría agregar un comentario: una razón por la que me sentí tentado a usar la lista de argumentos en mi caso fue tener un comportamiento coherente con el JavaScript construido en el método array.map.

Christophe
fuente
2
La segunda opción te da argumentos con nombre, lo cual es algo agradable en mi opinión.
Werner Kvalem Vesterås
¿Son argumentos opcionales o requeridos?
Odio a los perezosos el
@ user1689607 en mi ejemplo, los tres últimos son opcionales.
Christophe
Debido a que sus últimos dos argumentos son muy similares, si el usuario pasa solo uno u otro, nunca podrá saber cuál fue el objetivo. Debido a eso, casi necesitarías argumentos con nombre. Pero puedo apreciar que desea mantener una API similar a la API nativa.
Odio a los perezosos el
1
Modelar después de la API nativa no es algo malo, si su función hace algo similar. Todo se reduce a "lo que hace que el código sea más legible". Array.prototype.maptiene una API simple que no debería dejar a ningún codificador semi-experimentado desconcertado.
Jeremy J Starcher

Respuestas:

160

Como muchos de los otros, a menudo prefiero pasar un options objecta una función en lugar de pasar una larga lista de parámetros, pero realmente depende del contexto exacto.

Utilizo la legibilidad del código como prueba de fuego.

Por ejemplo, si tengo esta función, llame a:

checkStringLength(inputStr, 10);

Creo que el código es bastante legible tal como está y pasar parámetros individuales está bien.

Por otro lado, hay funciones con llamadas como esta:

initiateTransferProtocol("http", false, 150, 90, null, true, 18);

Completamente ilegible a menos que investigue un poco. Por otro lado, este código se lee bien:

initiateTransferProtocol({
  "protocol": "http",
  "sync":      false,
  "delayBetweenRetries": 150,
  "randomVarianceBetweenRetries": 90,
  "retryCallback": null,
  "log": true,
  "maxRetries": 18
 });

Es más un arte que una ciencia, pero si tuviera que nombrar reglas generales:

Use un parámetro de opciones si:

  • Tienes más de cuatro parámetros.
  • Cualquiera de los parámetros son opcionales.
  • Alguna vez ha tenido que buscar la función para averiguar qué parámetros se necesitan
  • Si alguien alguna vez intenta estrangularte mientras grita "¡ARRRRRG!"
Jeremy J Starcher
fuente
10
Gran respuesta. Depende. Cuidado con las trampas booleanas ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html
Trevor Dixon
2
Oh sí ... me había olvidado de ese enlace. Realmente me hizo repensar cómo funcionan las API e incluso reescribí varias piezas de código después de saber que hice las cosas tontas. ¡Gracias!
Jeremy J Starcher
1
'Alguna vez ha tenido que buscar la función para descubrir qué parámetros se necesitan'. En su lugar, al usar un objeto de opciones, no siempre tendrá que buscar el método para descubrir que las teclas son necesarias y cómo se llaman. Intellisense en el IDE no muestra esta información, mientras que los parámetros sí. En la mayoría de los IDE, simplemente puede pasar el mouse sobre el método y le mostrará cuáles son los parámetros.
simbolo
1
Si están interesados ​​en las consecuencias de rendimiento de este poco de 'mejores prácticas de codificación', aquí hay un jsPerf que prueba en ambos sentidos: jsperf.com/function-boolean-arguments-vs-options-object/10 Tenga en cuenta el pequeño giro en el tercera alternativa, donde uso un objeto de opciones 'preestablecidas' (constantes), que se puede hacer cuando tiene muchas llamadas (durante la vida útil de su tiempo de ejecución, por ejemplo, su página web) con la misma configuración, conocida en el momento del desarrollo (en resumen : cuando los valores de sus opciones están un poco codificados en su código fuente).
Ger Hobbelt
2
@Sean Honestamente, ya no uso este estilo de codificación. Me cambié a TypeScript y uso de parámetros con nombre.
Jeremy J Starcher
28

Los argumentos múltiples son principalmente para parámetros obligatorios. No hay nada malo con ellos.

Si tiene parámetros opcionales, se complica. Si uno de ellos se basa en los demás, para que tengan un cierto orden (por ejemplo, el cuarto necesita el tercero), aún debe usar múltiples argumentos. Casi todos los métodos nativos de EcmaScript y DOM funcionan así. Un buen ejemplo es el openmétodo de solicitudes XMLHTTP , donde los últimos 3 argumentos son opcionales: la regla es como "sin contraseña sin un usuario" (consulte también los documentos MDN ).

Los objetos opcionales son útiles en dos casos:

  • Tiene tantos parámetros que se vuelve confuso: el "nombre" lo ayudará, no tiene que preocuparse por el orden de ellos (especialmente si pueden cambiar)
  • Tienes parámetros opcionales. Los objetos son muy flexibles y, sin ningún orden, simplemente pasa las cosas que necesita y nada más undefined.

En su caso, lo recomendaría map(nodeList, callback, options). nodelisty callbackson obligatorios, los otros tres argumentos se presentan solo ocasionalmente y tienen valores predeterminados razonables.

Otro ejemplo es JSON.stringify. Es posible que desee utilizar el spaceparámetro sin pasar una replacerfunción; luego debe llamar …, null, 4). Un objeto de argumentos podría haber sido mejor, aunque no es realmente razonable para solo 2 parámetros.

Bergi
fuente
+1 misma pregunta que @ trevor-dixon: ¿has visto esta mezcla utilizada en la práctica, por ejemplo, en las bibliotecas js?
Christophe
Un ejemplo podría ser los métodos ajax de jQuery . Aceptan la URL [obligatoria] como el primer argumento, y un argumento de grandes opciones como el segundo.
Bergi
¡tan raro! Nunca me había dado cuenta de esto antes. Siempre lo he visto usado con la URL como una propiedad de opción ...
Christophe
Sí, jQuery hace cosas raras con sus parámetros opcionales mientras es compatible con versiones anteriores :-)
Bergi
1
En mi opinión, esta es la única respuesta sensata aquí.
Benjamin Gruenbaum
11

Usar el enfoque de 'opciones como objeto' será lo mejor. No tiene que preocuparse por el orden de las propiedades y hay más flexibilidad en los datos que se pasan (parámetros opcionales, por ejemplo)

Crear un objeto también significa que las opciones podrían usarse fácilmente en múltiples funciones:

options={
    nodeList:...,
    callback:...,
    thisObject:...,
    fromIndex:...,
    toIndex:...
}

function1(options){
    alert(options.nodeList);
}

function2(options){
    alert(options.fromIndex);
}
Fluidbyte
fuente
La suposición (razonable) aquí es que el objeto siempre tendrá los mismos pares de claves. Si está trabajando con una API basura / inconsistente, entonces tiene un problema diferente en sus manos.
backdesk
9

Creo que si está creando una instancia de algo o llamando a un método de un objeto, desea utilizar un objeto de opciones. Si es una función que opera en uno o dos parámetros y devuelve un valor, es preferible una lista de argumentos.

En algunos casos, es bueno usar ambos. Si su función tiene uno o dos parámetros obligatorios y varios opcionales, haga que los dos primeros parámetros sean obligatorios y el tercero un hash de opciones opcional.

En tu ejemplo, lo haría map(nodeList, callback, options). Nodelist y callback son obligatorios, es bastante fácil saber lo que sucede con solo leer una llamada, y es como las funciones de mapa existentes. Cualquier otra opción se puede pasar como un tercer parámetro opcional.

Trevor Dixon
fuente
+1 interesante. ¿Lo has visto usado en la práctica, por ejemplo en las bibliotecas js?
Christophe
7

Tu comentario sobre la pregunta:

en mi ejemplo los tres últimos son opcionales.

Entonces, ¿por qué no hacer esto? (Nota: este es un Javascript bastante crudo. Normalmente usaría un defaulthash y lo actualizaría con las opciones pasadas usando Object.extend o JQuery.extend o similar ...)

function map(nodeList, callback, options) {
   options = options || {};
   var thisObject = options.thisObject || {};
   var fromIndex = options.fromIndex || 0;
   var toIndex = options.toIndex || 0;
}

Entonces, ahora que ahora es mucho más obvio qué es opcional y qué no, todos estos son usos válidos de la función:

map(nodeList, callback);
map(nodeList, callback, {});
map(nodeList, callback, null);
map(nodeList, callback, {
   thisObject: {some: 'object'},
});
map(nodeList, callback, {
   toIndex: 100,
});
map(nodeList, callback, {
   thisObject: {some: 'object'},
   fromIndex: 0,
   toIndex: 100,
});
Izkata
fuente
Esto es similar a la respuesta de @ trevor-dixon.
Christophe
5

Puede que llegue un poco tarde a la fiesta con esta respuesta, pero estaba buscando las opiniones de otros desarrolladores sobre este mismo tema y encontré este hilo.

Estoy muy en desacuerdo con la mayoría de los que respondieron, y estoy del lado del enfoque de 'argumentos múltiples'. Mi argumento principal es que desalienta otros antipatrones como "mutar y devolver el objeto param" o "pasar el mismo objeto param a otras funciones". He trabajado en bases de código que han abusado ampliamente de este antipatrón, y la depuración de código que lo hace rápidamente se vuelve imposible. Creo que esta es una regla práctica muy específica de Javascript, ya que Javascript no está fuertemente tipado y permite tales objetos estructurados arbitrariamente.

Mi opinión personal es que los desarrolladores deben ser explícitos al llamar a funciones, evitar transmitir datos redundantes y evitar modificar por referencia. No es que estos patrones impidan escribir código conciso y correcto. Siento que hace que sea mucho más fácil para su proyecto caer en malas prácticas de desarrollo.

Considere el siguiente código terrible:

function main() {
    const x = foo({
        param1: "something",
        param2: "something else",
        param3: "more variables"
    });

    return x;
}

function foo(params) {
    params.param1 = "Something new";
    bar(params);
    return params;
}


function bar(params) {
    params.param2 = "Something else entirely";
    const y = baz(params);
    return params.param2;
}

function baz(params) {
    params.params3 = "Changed my mind";
    return params;
}

No solo requiere este tipo de documentación más explícita para especificar la intención, sino que también deja espacio para errores vagos. ¿Qué pasa si un desarrollador modifica param1en bar()? ¿Cuánto tiempo crees que tomaría mirar a través de una base de código de tamaño suficiente para atrapar esto? Es cierto que este ejemplo es un poco falso porque asume que los desarrolladores ya han cometido varios antipatrones en este momento. Pero muestra cómo pasar objetos que contienen parámetros permite un mayor margen de error y ambigüedad, lo que requiere un mayor grado de conciencia y observancia de la corrección constante.

¡Solo mis dos centavos sobre el tema!

ajxs
fuente
3

Depende.

Según mi observación sobre el diseño de esas bibliotecas populares, estos son los escenarios en los que deberíamos usar el objeto de opción:

  • La lista de parámetros es larga (> 4).
  • Algunos o todos los parámetros son opcionales y no dependen de un cierto orden.
  • La lista de parámetros podría crecer en futuras actualizaciones de la API.
  • Se llamará a la API desde otro código y el nombre de la API no es lo suficientemente claro como para indicar el significado de los parámetros. Por lo tanto, podría necesitar un nombre de parámetro fuerte para facilitar la lectura.

Y escenarios para usar la lista de parámetros:

  • La lista de parámetros es corta (<= 4).
  • La mayoría o la totalidad de los parámetros son obligatorios.
  • Los parámetros opcionales están en un cierto orden. (es decir: $ .get)
  • Fácil de decir los parámetros que significan por nombre de API.
Lynn Ning
fuente
2

El objeto es más preferible, porque si pasa un objeto es fácil ampliar el número de propiedades en esos objetos y no tiene que estar atento al orden en que se han pasado sus argumentos.

happyCoda
fuente
1

Para una función que generalmente usa algunos argumentos predefinidos, será mejor que use el objeto de opción. El ejemplo opuesto será algo así como una función que obtiene un número infinito de argumentos como: setCSS ({height: 100}, {width: 200}, {background: "# 000"}).

Ilya Sidorovich
fuente
0

Me gustaría ver grandes proyectos de JavaScript.

Cosas como google map verá con frecuencia que los objetos instanciados requieren un objeto pero las funciones requieren parámetros. Creo que esto tiene que ver con los argumentos de OPTION.

Si necesita argumentos predeterminados o argumentos opcionales, un objeto probablemente sería mejor porque es más flexible. Pero si no lo hace, los argumentos funcionales normales son más explícitos.

Javascript también tiene un argumentsobjeto. https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments

dm03514
fuente