Objets d'arguments multiples et d'options

157

Lors de la création d'une fonction JavaScript avec plusieurs arguments, je suis toujours confronté à ce choix: passer une liste d'arguments vs passer un objet d'options.

Par exemple, j'écris une fonction pour mapper une nodeList à un tableau:

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

Je pourrais plutôt utiliser ceci:

function map(options){
    ...
}

où options est un objet:

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

Quelle est la méthode recommandée? Existe-t-il des directives pour savoir quand utiliser l'un ou l'autre?

[Mise à jour] Il semble y avoir un consensus en faveur de l'objet options, donc j'aimerais ajouter un commentaire: une des raisons pour lesquelles j'ai été tenté d'utiliser la liste d'arguments dans mon cas était d'avoir un comportement cohérent avec le JavaScript intégrée à la méthode array.map.

Christophe
la source
2
La deuxième option vous donne des arguments nommés, ce qui est une bonne chose à mon avis.
Werner Kvalem Vesterås
Sont-ils des arguments facultatifs ou obligatoires?
Je déteste paresseux
@ user1689607 dans mon exemple, les trois derniers sont facultatifs.
Christophe
Parce que vos deux derniers arguments sont très similaires, si l'utilisateur ne passait que l'un ou l'autre, vous ne seriez jamais vraiment en mesure de savoir lequel était destiné. Pour cette raison, vous auriez presque besoin d'arguments nommés. Mais je comprends que vous souhaitiez conserver une API similaire à l'API native.
Je déteste paresseux
1
Modéliser après l'API native n'est pas une mauvaise chose, si votre fonction fait quelque chose de similaire. Tout se résume à «ce qui rend le code le plus lisible». Array.prototype.mapa une API simple qui ne devrait laisser perplexe aucun codeur semi-expérimenté.
Jeremy J Starcher

Réponses:

160

Comme beaucoup d'autres, je préfère souvent passer un options objectà une fonction au lieu de passer une longue liste de paramètres, mais cela dépend vraiment du contexte exact.

J'utilise la lisibilité du code comme test décisif.

Par exemple, si j'ai cet appel de fonction:

checkStringLength(inputStr, 10);

Je pense que le code est tout à fait lisible tel qu'il est et que passer des paramètres individuels est très bien.

D'autre part, il existe des fonctions avec des appels comme celui-ci:

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

Complètement illisible à moins que vous ne fassiez des recherches. En revanche, ce code se lit bien:

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

C'est plus un art qu'une science, mais si je devais nommer des règles empiriques:

Utilisez un paramètre d'options si:

  • Vous avez plus de quatre paramètres
  • Tous les paramètres sont facultatifs
  • Vous avez déjà eu à rechercher la fonction pour déterminer les paramètres nécessaires
  • Si quelqu'un essaie de vous étrangler en criant "ARRRRRG!"
Jeremy J Starcher
la source
10
Très bonne réponse. Ça dépend. Méfiez-vous des pièges booléens ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html
Trevor Dixon
2
Oh oui ... j'avais oublié ce lien. Cela m'a vraiment fait repenser le fonctionnement des API et j'ai même réécrit plusieurs morceaux de code après avoir appris que je faisais des choses stupides. Merci!
Jeremy J Starcher
1
«Vous avez déjà eu à rechercher la fonction pour déterminer quels paramètres elle prend» - En utilisant un objet d'options à la place, vous n'aurez pas toujours à rechercher la méthode pour comprendre que les clés sont nécessaires et comment elles sont appelées. Intellisense dans l'EDI ne fait pas apparaître ces informations, contrairement aux paramètres. Dans la plupart des IDE, vous pouvez simplement passer la souris sur la méthode et cela vous montrera quels sont les paramètres.
simbolo
1
Si vous êtes tous intéressés par les conséquences sur les performances de cette partie des `` meilleures pratiques de codage '', voici un jsPerf testant les deux façons: jsperf.com/function-boolean-arguments-vs-options-object/10 Notez la petite torsion dans le troisième alternative, où j'utilise un objet d'options 'preset' (constant), ce qui peut être fait lorsque vous avez beaucoup d'appels (pendant la durée de vie de votre runtime, par exemple votre page Web) avec les mêmes paramètres, connus au moment du développement (en bref : lorsque les valeurs de vos options sont un peu codées en dur dans votre code source).
Ger Hobbelt
2
@Sean Honnêtement, je n'utilise plus du tout ce style de codage. Je suis passé à TypeScript et à l'utilisation de paramètres nommés.
Jeremy J Starcher
28

Les arguments multiples concernent principalement les paramètres obligatoires. Il n'y a rien de mal avec eux.

Si vous avez des paramètres optionnels, cela devient compliqué. Si l'un d'eux s'appuie sur les autres, pour qu'ils aient un certain ordre (par exemple, le quatrième a besoin du troisième), vous devez toujours utiliser plusieurs arguments. Presque toutes les méthodes natives EcmaScript et DOM fonctionnent comme ceci. Un bon exemple est la openméthode des requêtes XMLHTTP , où les 3 derniers arguments sont facultatifs - la règle est comme "pas de mot de passe sans utilisateur" (voir aussi la documentation MDN ).

Les objets Option sont utiles dans deux cas:

  • Vous avez tellement de paramètres que cela devient déroutant: le "nommage" vous aidera, vous n'avez pas à vous soucier de leur ordre (surtout s'ils peuvent changer)
  • Vous avez des paramètres facultatifs. Les objets sont très flexibles, et sans aucun ordre vous passez simplement les choses dont vous avez besoin et rien d'autre (ou undefineds).

Dans votre cas, je recommande map(nodeList, callback, options). nodelistet callbacksont obligatoires, les trois autres arguments ne viennent qu'occasionnellement et ont des défauts raisonnables.

Un autre exemple est JSON.stringify. Vous voudrez peut-être utiliser le spaceparamètre sans passer une replacerfonction - alors vous devez appeler …, null, 4). Un objet arguments aurait pu être meilleur, bien que ce ne soit pas vraiment raisonnable pour seulement 2 paramètres.

Bergi
la source
+1 même question que @ trevor-dixon: avez-vous vu ce mix utilisé en pratique, par exemple dans les bibliothèques js?
Christophe
Un exemple pourrait être les méthodes ajax de jQuery . Ils acceptent l'URL [obligatoire] comme premier argument, et un énorme argument d'options comme second.
Bergi
si étrange! Je n'ai jamais remarqué cela auparavant. Je l'ai toujours vu utilisé avec l'url comme propriété d'option ...
Christophe
Oui, jQuery fait des choses bizarres avec ses paramètres optionnels tout en restant rétrocompatible :-)
Bergi
1
À mon avis, c'est la seule réponse sensée ici.
Benjamin Gruenbaum
11

L'utilisation de l'approche «options en tant qu'objet» sera la meilleure solution. Vous n'avez pas à vous soucier de l'ordre des propriétés et il y a plus de flexibilité dans les données transmises (paramètres optionnels par exemple)

Créer un objet signifie également que les options peuvent être facilement utilisées sur plusieurs fonctions:

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

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

function2(options){
    alert(options.fromIndex);
}
Fluidbyte
la source
L'hypothèse (raisonnable) ici est que l'objet aura toujours les mêmes paires de clés. Si vous travaillez avec une API de merde / incohérente, vous avez un problème différent entre vos mains.
backdesk
9

Je pense que si vous instanciez quelque chose ou appelez une méthode d'un objet, vous voulez utiliser un objet options. S'il s'agit d'une fonction qui n'opère que sur un ou deux paramètres et renvoie une valeur, une liste d'arguments est préférable.

Dans certains cas, il est bon d'utiliser les deux. Si votre fonction a un ou deux paramètres obligatoires et un tas de paramètres facultatifs, rendez les deux premiers paramètres obligatoires et le troisième un hachage d'options facultatif.

Dans votre exemple, je le ferais map(nodeList, callback, options). Nodelist et callback sont nécessaires, il est assez facile de dire ce qui se passe simplement en lisant un appel, et c'est comme les fonctions de carte existantes. Toutes les autres options peuvent être passées en tant que troisième paramètre facultatif.

Trevor Dixon
la source
+1 intéressant. L'avez-vous vu utilisé dans la pratique, par exemple dans les bibliothèques js?
Christophe
7

Votre commentaire sur la question:

dans mon exemple, les trois derniers sont facultatifs.

Alors pourquoi ne pas faire ça? (Remarque: c'est du Javascript assez brut. Normalement, j'utiliserais un defaulthachage et le mettrais à jour avec les options transmises en utilisant Object.extend ou JQuery.extend ou similaire ..)

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

Donc, maintenant qu'il est maintenant beaucoup plus évident de savoir ce qui est facultatif et ce qui ne l'est pas, tous ces éléments sont des utilisations valides de la fonction:

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
la source
Ceci est similaire à la réponse de @ trevor-dixon.
Christophe
5

Je suis peut-être un peu en retard à la fête avec cette réponse, mais je cherchais les opinions d'autres développeurs sur ce sujet même et je suis tombé sur ce fil.

Je ne suis pas du tout d'accord avec la plupart des intervenants et je partage l'approche des «arguments multiples». Mon argument principal étant qu'il décourage d'autres anti-patterns comme "muter et renvoyer l'objet param", ou "passer le même objet param à d'autres fonctions". J'ai travaillé dans des bases de code qui ont largement abusé de cet anti-pattern, et le débogage de code qui fait cela devient rapidement impossible. Je pense que c'est une règle empirique très spécifique à Javascript, car Javascript n'est pas fortement typé et permet de tels objets structurés de manière arbitraire.

Mon opinion personnelle est que les développeurs doivent être explicites lorsqu'ils appellent des fonctions, éviter de transmettre des données redondantes et éviter de modifier par référence. Ce n'est pas que ce modèle empêche l'écriture de code concis et correct. Je pense simplement qu'il est beaucoup plus facile pour votre projet de tomber dans de mauvaises pratiques de développement.

Considérez le terrible code suivant:

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 seulement ce type d'exigence nécessite une documentation plus explicite pour spécifier l'intention, mais il laisse également la place à de vagues erreurs. Que faire si un développeur modifie param1en bar()? Combien de temps pensez-vous qu'il faudrait parcourir une base de code de taille suffisante pour attraper cela? Certes, cet exemple est légèrement malhonnête car il suppose que les développeurs ont déjà commis plusieurs anti-modèles à ce stade. Mais cela montre comment le passage d'objets contenant des paramètres permet une plus grande marge d'erreur et d'ambiguïté, nécessitant un plus grand degré de conscience et le respect de l'exactitude des constats.

Juste mes deux cents sur la question!

ajxs
la source
3

Ça dépend.

Sur la base de mon observation sur la conception de ces bibliothèques populaires, voici les scénarios que nous devrions utiliser objet option:

  • La liste des paramètres est longue (> 4).
  • Certains ou tous les paramètres sont facultatifs et ne dépendent pas d'un certain ordre.
  • La liste des paramètres pourrait augmenter dans une future mise à jour de l'API.
  • L'API sera appelée à partir d'un autre code et le nom de l'API n'est pas assez clair pour indiquer la signification des paramètres. Il peut donc avoir besoin d'un nom de paramètre fort pour la lisibilité.

Et les scénarios pour utiliser la liste des paramètres:

  • La liste des paramètres est courte (<= 4).
  • La plupart ou tous les paramètres sont obligatoires.
  • Les paramètres facultatifs sont dans un certain ordre. (c'est-à-dire: $ .get)
  • Facile à dire la signification des paramètres par nom d'API.
Lynn Ning
la source
2

L'objet est préférable, car si vous passez un objet, il est facile d'étendre le nombre de propriétés dans ces objets et vous n'avez pas à surveiller l'ordre dans lequel vos arguments ont été passés.

happyCoda
la source
1

Pour une fonction qui utilise généralement des arguments prédéfinis, vous feriez mieux d'utiliser l'objet option. L'exemple opposé sera quelque chose comme une fonction qui obtient un nombre infini d'arguments comme: setCSS ({height: 100}, {width: 200}, {background: "# 000"}).

Ilya Sidorovich
la source
0

Je regarderais de grands projets javascript.

Des choses comme google map, vous verrez fréquemment que les objets instanciés nécessitent un objet mais que les fonctions nécessitent des paramètres. Je pense que cela a à voir avec les arguments OPTION.

Si vous avez besoin d'arguments par défaut ou d'arguments optionnels, un objet serait probablement meilleur car il est plus flexible. Mais si vous ne le faites pas, les arguments fonctionnels normaux sont plus explicites.

Javascript a aussi un argumentsobjet. https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments

dm03514
la source