Argomenti multipli vs. oggetto opzioni


157

Quando creo una funzione JavaScript con più argomenti, mi trovo sempre di fronte a questa scelta: passare un elenco di argomenti e passare un oggetto opzioni.

Ad esempio, sto scrivendo una funzione per mappare un nodeList a un array:

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

Potrei invece usare questo:

function map(options){
    ...
}

dove options è un oggetto:

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

Qual è il modo raccomandato? Ci sono linee guida per quando usare l'una rispetto all'altra?

[Aggiornamento] Sembra esserci un consenso a favore dell'oggetto opzioni, quindi vorrei aggiungere un commento: uno dei motivi per cui sono stato tentato di utilizzare l'elenco degli argomenti nel mio caso è stato quello di avere un comportamento coerente con JavaScript metodo array.map integrato.


2
La seconda opzione ti dà argomenti chiamati, il che è una cosa carina secondo me.
Werner Kvalem Vesterås,

Sono argomenti opzionali o obbligatori?
I Hate Lazy,

@ user1689607 nel mio esempio gli ultimi tre sono opzionali.
Christophe,

Poiché i tuoi ultimi due argomenti sono molto simili, se l'utente passasse solo l'uno o l'altro, non potresti mai sapere quale era destinato. Per questo motivo, avresti quasi bisogno di argomenti con nome. Ma posso apprezzare che vorresti mantenere un'API simile all'API nativa.
I Hate Lazy,

1
Modellare dopo l'API nativa non è un male, se la tua funzione fa qualcosa di simile. Tutto si riduce a "ciò che rende il codice più leggibile". Array.prototype.mapha una semplice API che non deve lasciare perplessi i programmatori semi esperti.
Jeremy J Starcher,

Risposte:


160

Come molti altri, spesso preferisco passare options objecta una funzione anziché passare un lungo elenco di parametri, ma dipende davvero dal contesto esatto.

Uso la leggibilità del codice come cartina di tornasole.

Ad esempio, se ho questa chiamata di funzione:

checkStringLength(inputStr, 10);

Penso che il codice sia abbastanza leggibile così com'è e il passaggio dei singoli parametri va bene.

D'altra parte, ci sono funzioni con chiamate come questa:

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

Completamente illeggibile a meno che tu non faccia qualche ricerca. D'altra parte, questo codice recita bene:

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

È più un'arte che una scienza, ma se dovessi nominare le regole empiriche:

Utilizzare un parametro opzioni se:

  • Hai più di quattro parametri
  • Tutti i parametri sono opzionali
  • Hai mai dovuto cercare la funzione per capire quali parametri prende
  • Se qualcuno cerca di strangolarti mentre urla "ARRRRRG!"

10
Bella risposta. Dipende. Attenzione alle trappole booleane ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html
Trevor Dixon,

2
Oh sì ... mi ero dimenticato di quel link. Mi ha fatto davvero ripensare il modo in cui funzionano le API e ho anche riscritto diverse parti di codice dopo aver appreso che ho fatto cose stupide. Grazie!
Jeremy J Starcher,

1
'Non hai mai dovuto cercare la funzione per capire quali parametri sono necessari' - Usando invece un oggetto options, non dovresti sempre cercare il metodo per capire che i tasti sono necessari e come vengono chiamati. Intellisense nell'IDE non riporta queste informazioni, mentre i parametri lo fanno. Nella maggior parte degli IDE, puoi semplicemente passare il mouse sopra il metodo e ti mostrerà quali sono i parametri.
simbolo

1
Se siete interessati alle conseguenze sulle prestazioni di questo bit di "buone pratiche di codifica", ecco un test jsPerf in entrambi i modi: jsperf.com/function-boolean-arguments-vs-options-object/10 Nota la piccola svolta nel terza alternativa, in cui utilizzo un oggetto opzioni 'predefinito' (costante), che può essere fatto quando hai molte chiamate (durante la durata del tuo runtime, ad es. la tua pagina web) con le stesse impostazioni, note al momento dello sviluppo (in breve : quando i valori delle opzioni sono in qualche modo codificati nel codice sorgente).
Ger Hobbelt,

2
@Sean Onestamente, non uso più questo stile di programmazione. Sono passato a TypeScript e utilizzo dei parametri con nome.
Jeremy J Starcher,

28

Argomenti multipli sono principalmente per parametri obbligatori. Non c'è niente di sbagliato in loro.

Se hai parametri opzionali, diventa complicato. Se uno di essi si basa sugli altri, in modo che abbiano un certo ordine (ad esempio il quarto ha bisogno del terzo), è comunque necessario utilizzare più argomenti. Quasi tutti i metodi nativi EcmaScript e DOM funzionano così. Un buon esempio è il openmetodo delle richieste XMLHTTP , in cui gli ultimi 3 argomenti sono opzionali: la regola è come "nessuna password senza un utente" (vedere anche i documenti MDN ).

Gli oggetti opzione sono utili in due casi:

  • Hai così tanti parametri che diventa confuso: la "denominazione" ti aiuterà, non devi preoccuparti dell'ordine di loro (specialmente se possono cambiare)
  • Hai parametri opzionali. Gli oggetti sono molto flessibili e senza alcun ordine passi semplicemente le cose di cui hai bisogno e nient'altro undefined.

Nel tuo caso, lo consiglierei map(nodeList, callback, options). nodeliste callbacksono richiesti, gli altri tre argomenti entrano solo occasionalmente e hanno valori di default ragionevoli.

Un altro esempio è JSON.stringify. Potresti voler usare il spaceparametro senza passare una replacerfunzione, quindi devi chiamare …, null, 4). Un oggetto argomenti potrebbe essere stato migliore, sebbene non sia veramente ragionevole per solo 2 parametri.


+1 stessa domanda di @ trevor-dixon: hai visto questo mix usato in pratica, ad esempio nelle librerie js?
Christophe,

Un esempio potrebbe essere i metodi ajax di jQuery . Accettano l'URL [obbligatorio] come primo argomento e un enorme argomento di opzioni come secondo argomento.
Bergi,

così strano! Non l'ho mai notato prima. L'ho sempre visto usato con l'URL come proprietà dell'opzione ...
Christophe,

Sì, jQuery fa una cosa strana con i suoi parametri opzionali rimanendo compatibile con le versioni precedenti :-)
Bergi,

1
Secondo me, questa è l'unica risposta sana qui.
Benjamin Gruenbaum,

11

Utilizzare l'approccio "opzioni come oggetto" sarà la cosa migliore. Non devi preoccuparti dell'ordine delle proprietà e c'è più flessibilità in quali dati vengono passati (parametri opzionali per esempio)

La creazione di un oggetto significa anche che le opzioni potrebbero essere facilmente utilizzate su più funzioni:

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

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

function2(options){
    alert(options.fromIndex);
}

Il presupposto (ragionevole) qui è che l'oggetto avrà sempre le stesse coppie di chiavi. Se stai lavorando con un'API merda / incoerente, hai un problema diverso tra le mani.
backdesk

9

Penso che se stai istanziando qualcosa o chiamando un metodo di un oggetto, vuoi usare un oggetto opzioni. Se è una funzione che opera solo su uno o due parametri e restituisce un valore, è preferibile un elenco di argomenti.

In alcuni casi, è bene usare entrambi. Se la tua funzione ha uno o due parametri richiesti e un mucchio di parametri opzionali, imposta i primi due parametri richiesti e il terzo un hash opzionale di opzioni.

Nel tuo esempio, lo farei map(nodeList, callback, options). Nodelist e callback sono richiesti, è abbastanza facile dire cosa sta succedendo semplicemente leggendo una chiamata, ed è come le funzioni della mappa esistenti. Qualsiasi altra opzione può essere passata come terzo parametro opzionale.


+1 interessante. L'hai visto usato in pratica, ad esempio nelle librerie js?
Christophe,

7

Il tuo commento sulla domanda:

nel mio esempio gli ultimi tre sono opzionali.

Quindi perché non farlo? (Nota: questo è Javascript piuttosto grezzo. Normalmente userei un defaulthash e lo aggiornerei con le opzioni passate usando Object.extend o JQuery.extend o simili ..)

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

Quindi, dato che ora è molto più ovvio ciò che è facoltativo e ciò che non lo è, tutti questi sono usi validi della funzione:

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

Questo è simile alla risposta di @ trevor-dixon.
Christophe,

5

Potrei essere un po 'in ritardo alla festa con questa risposta, ma stavo cercando le opinioni di altri sviluppatori su questo argomento e mi sono imbattuto in questo thread.

Sono in gran parte in disaccordo con la maggior parte dei soccorritori e mi schierò con l'approccio degli "argomenti multipli". Il mio argomento principale è che scoraggia altri anti-pattern come "mutare e restituire l'oggetto param" o "passare lo stesso oggetto param ad altre funzioni". Ho lavorato su basi di codice che hanno ampiamente abusato di questo anti-pattern e il debugging del codice che lo fa rapidamente diventa impossibile. Penso che questa sia una regola empirica molto specifica di Javascript, poiché Javascript non è fortemente tipizzato e consente tali oggetti strutturati in modo arbitrario.

La mia opinione personale è che gli sviluppatori dovrebbero essere espliciti quando chiamano le funzioni, evitare di passare dati ridondanti ed evitare modifiche per riferimento. Non è che questo schema precluda la scrittura di codice conciso e corretto. Sento solo che rende molto più facile per il tuo progetto cadere in cattive pratiche di sviluppo.

Considera il seguente codice terribile:

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

Non solo questo tipo di documentazione richiede una documentazione più esplicita per specificare l'intento, ma lascia anche spazio a vaghi errori. Cosa succede se un modifica sviluppatore param1a bar()? Quanto pensi che ci vorrebbe guardare attraverso una base di codice di dimensioni sufficienti per catturare questo? Certo, questo esempio è leggermente disonesto perché presume che gli sviluppatori abbiano già commesso diversi anti-schemi a questo punto. Ma mostra come il passaggio di oggetti contenenti parametri consenta una maggiore possibilità di errore e ambiguità, richiedendo un maggior grado di coscienza e rispetto della correttezza costante.

Solo i miei due centesimi sulla questione!


3

Dipende.

Sulla base della mia osservazione sul design di quelle librerie popolari, ecco gli scenari che dovremmo usare l'opzione object:

  • L'elenco dei parametri è lungo (> 4).
  • Alcuni o tutti i parametri sono opzionali e non si basano su un determinato ordine.
  • L'elenco dei parametri potrebbe aumentare nel futuro aggiornamento dell'API.
  • L'API verrà chiamata da un altro codice e il nome dell'API non è abbastanza chiaro da dire il significato dei parametri. Quindi potrebbe essere necessario un nome di parametro forte per la leggibilità.

E scenari per utilizzare l'elenco dei parametri:

  • L'elenco dei parametri è breve (<= 4).
  • Sono richiesti la maggior parte o tutti i parametri.
  • I parametri opzionali sono in un certo ordine. (ad es .: $ .get)
  • Facile distinguere i parametri dal nome dell'API.

2

L'oggetto è più preferibile, perché se si passa un oggetto è facile estendere il numero di proprietà in quegli oggetti e non è necessario controllare l'ordine in cui sono stati passati gli argomenti.


1

Per una funzione che di solito utilizza alcuni argomenti predefiniti, è meglio utilizzare l'oggetto opzione. L'esempio opposto sarà qualcosa come una funzione che sta ottenendo un numero infinito di argomenti come: setCSS ({altezza: 100}, {larghezza: 200}, {sfondo: "# 000"}).


0

Vorrei guardare grandi progetti javascript.

Cose come google map vedrai spesso che gli oggetti istanziati richiedono un oggetto ma le funzioni richiedono parametri. Penso che ciò abbia a che fare con gli argomenti OPTION.

Se hai bisogno di argomenti predefiniti o argomenti opzionali, probabilmente un oggetto sarebbe migliore perché è più flessibile. Ma se non le normali argomentazioni funzionali sono più esplicite.

Anche Javascript ha un argumentsoggetto. https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.