Pourquoi de nombreuses langues ne prennent-elles pas en charge les paramètres nommés? [fermé]

14

Je pensais à quel point il serait plus facile de lire du code si, lors de l'appel d'une fonction, vous pouviez écrire:

doFunction(param1=something, param2=somethingElse);

Je ne peux penser à aucun inconvénient et cela rendrait le code beaucoup plus lisible. Je sais que vous pourriez passer un tableau comme seul argument et avoir les clés du tableau comme noms de paramètres, mais ce ne serait toujours pas aussi lisible.

Y a-t-il un inconvénient qui me manque? Sinon, pourquoi de nombreuses langues ne le permettent-elles pas?


la source
1
pouvez-vous donner un langage qui devrait (et peut) avoir des paramètres nommés mais ne l'a pas?
Bryan Chen
1
Je pense que c'est trop verbeux dans de nombreux cas. Pour autant que je l'ai vu, le fait qu'une langue l'utilise ou non a tendance à dépendre de son impact positif ou négatif. La plupart des langages de style C s'en sortent bien. Dans leur cas, il est souvent assez évident de savoir ce qui se passe de toute façon, et son absence aide vraiment à réduire l'encombrement en général.
Panzercrisis
2
@SteveFallows: Le "Named Parameter Idiom" semble être un nom étrange pour la définition d'une API fluide (comme nommé par Martin Fowler) sur une classe de générateur. Vous pouvez trouver des exemples du même modèle dans l'un des premiers éléments de Effective Java de Joshua Bloch .
jhominal
3
Ce n'est pas nécessairement une question d'opinion
Catskul
2
D'accord. "Pourquoi de nombreuses langues ne prennent-elles pas en charge les paramètres nommés?" est plus une question d'histoire des langues que d'opinion. Ce serait une opinion si quelqu'un lui demandait "Les paramètres nommés ne sont-ils pas meilleurs?".
Andy Fleming

Réponses:

14

La spécification des noms de paramètres ne rend pas toujours le code plus lisible: à titre d'exemple, préférez-vous lire min(first=0, second=1)ou min(0, 1)?

Si vous acceptez l'argument du paragraphe précédent, il est assez évident que la spécification des noms de paramètres ne devrait pas être obligatoire. Pourquoi toutes les langues ne font-elles pas de la spécification des noms de paramètres une option valide?

Je peux penser à au moins deux raisons:

  • En général, l'introduction d'une seconde syntaxe pour un besoin déjà couvert (ici, passage de paramètres) est quelque chose dont les avantages doivent être mis en balance avec le coût inhérent à la complexité du langage introduit (à la fois dans l'implémentation du langage et dans le modèle mental du programmeur du langage) ;
  • Cela fait du changement de nom de paramètre un changement de rupture d'API, qui peut ne pas être conforme à l'attente d'un développeur que le changement de nom de paramètre soit un changement trivial et sans rupture;

Notez que je ne connais aucun langage qui implémente des paramètres nommés sans implémenter également des paramètres facultatifs (qui nécessitent des paramètres nommés) - vous devez donc vous garder de surestimer leurs avantages en termes de lisibilité, qui peuvent être obtenus de manière plus cohérente avec la définition des interfaces fluides .

jhominal
la source
6
Je code régulièrement dans plusieurs langues différentes, je dois presque toujours rechercher les paramètres d'une sous-chaîne simple dans une langue donnée. S'agit-il d'une sous-chaîne (début, longueur) ou d'une sous-chaîne (début, fin) et la fin est-elle incluse dans la chaîne?
James Anderson
1
@JamesAnderson - Comment une sous-chaîne à paramètres nommés résoudrait-elle votre problème? Vous devrez toujours rechercher les paramètres afin de savoir quel est le nom du deuxième paramètre (sauf si les deux fonctions existent et que la fonction de polymorphisme du langage autorise des paramètres de même type avec des noms différents).
mouviciel
6
@mouviciel: Les paramètres nommés ne sont pas du tout utiles pour l' auteur , mais sont fantastiques pour le lecteur . Nous savons tous à quel point les noms de variables sont importants pour créer du code lisible, et les paramètres nommés permettent au créateur d'une interface de donner de bons noms de paramètres ainsi que de bons noms de fonctions, ce qui facilite la tâche des personnes utilisant cette interface pour écrire du code lisible.
Phoshi
@Phoshi - Vous avez raison. J'ai juste oublié l'importance de lire le code lorsque j'ai écrit mon commentaire précédent.
mouviciel
14

Les paramètres nommés rendent le code plus facile à lire et plus difficile à écrire

Lorsque je lis un morceau de code, les paramètres nommés peuvent introduire un contexte qui rend le code plus facile à comprendre. Considérons par exemple ce constructeur: Color(1, 102, 205, 170). Qu'est-ce que ça veut dire? En effet, Color(alpha: 1, red: 102, green: 205, blue: 170)serait beaucoup plus facile à lire. Mais hélas, le compilateur dit «non» - il veut Color(a: 1, r: 102, g: 205, b: 170). Lorsque vous écrivez du code à l'aide de paramètres nommés, vous passez un temps inutile à rechercher les noms exacts - il est plus facile d'oublier les noms exacts de certains paramètres que d'oublier leur ordre.

Cela m'a une fois mordu lorsque j'utilisais une DateTimeAPI qui avait deux classes frères pour les points et les durées avec des interfaces presque identiques. Bien DateTime->new(...)accepté un second => 30argument, le DateTime::Duration->new(...)voulu seconds => 30, et similaire pour d'autres unités. Oui, c'est tout à fait logique, mais cela m'a montré que les paramètres nommés ≠ facilité d'utilisation.

Les mauvais noms ne facilitent même pas la lecture

Un autre exemple de la façon dont les paramètres nommés peuvent être mauvais est probablement le langage R. Ce morceau de code crée un tracé de données:

plot(plotdata$n, plotdata$mu, type="p", pch=17,  lty=1, bty="n", ann=FALSE, axes=FALSE)

Vous voyez deux arguments positionnels pour les lignes de données x et y , puis une liste de paramètres nommés. Il existe de nombreuses autres options avec des valeurs par défaut, et seules celles qui sont répertoriées dont je voulais modifier ou spécifier explicitement les valeurs par défaut. Une fois que nous ignorons que ce code utilise des nombres magiques et pourrait bénéficier de l'utilisation d'énumérations (si R en avait!), Le problème est que beaucoup de ces noms de paramètres sont plutôt indéchiffrables.

  • pchest en fait le caractère de tracé, le glyphe qui sera dessiné pour chaque point de données. 17est un cercle vide, ou quelque chose comme ça.
  • ltyest le type de ligne. Voici 1une ligne continue.
  • btyest le type de boîte. Le paramétrer pour "n"éviter qu'une boîte soit dessinée autour de l'intrigue.
  • ann contrôle l'apparence des annotations d'axe.

Pour quelqu'un qui ne sait pas ce que signifie chaque abréviation, ces options sont plutôt déroutantes. Cela révèle également pourquoi R utilise ces étiquettes: pas comme code auto-documenté, mais (étant un langage typé dynamiquement) comme clés pour mapper les valeurs à leurs variables correctes.

Propriétés des paramètres et des signatures

Les signatures de fonction peuvent avoir les propriétés suivantes:

  • Les arguments peuvent être ordonnés ou non,
  • nommé ou non,
  • requis ou facultatif.
  • Les signatures peuvent également être surchargées par taille ou type,
  • et peut avoir une taille non spécifiée avec varargs.

Différentes langues atterrissent à différentes coordonnées de ce système. En C, les arguments sont ordonnés, sans nom, toujours requis et peuvent être des varargs. En Java, la situation est similaire, sauf que les signatures peuvent être surchargées. Dans l'Objectif C, les signatures sont ordonnées, nommées, requises et ne peuvent pas être surchargées car il s'agit simplement de sucre syntaxique autour de C.

Les langages à typage dynamique avec varargs (interfaces de ligne de commande, Perl,…) peuvent émuler des paramètres nommés facultatifs. Les langues avec surcharge de taille de signature ont quelque chose comme des paramètres optionnels de position.

Comment ne pas implémenter les paramètres nommés

Lorsque nous pensons à des paramètres nommés, nous supposons généralement des paramètres nommés, facultatifs et non ordonnés. Leur mise en œuvre est difficile.

Les paramètres facultatifs peuvent avoir des valeurs par défaut. Ceux-ci doivent être spécifiés par la fonction appelée et ne doivent pas être compilés dans le code appelant. Sinon, les valeurs par défaut ne peuvent pas être mises à jour sans recompiler tout le code dépendant.

Maintenant, une question importante est de savoir comment les arguments sont réellement passés à la fonction. Avec les paramètres ordonnés, les arguments peuvent être passés dans un registre ou dans leur ordre inhérent sur la pile. Lorsque nous excluons les registres pendant un moment, le problème est de savoir comment mettre des arguments facultatifs non ordonnés sur la pile.

Pour cela, nous avons besoin d'un certain ordre sur les arguments facultatifs. Que faire si le code de déclaration est modifié? Comme l'ordre n'est pas pertinent, une réorganisation dans la déclaration de fonction ne doit pas modifier la position des valeurs sur la pile. Nous devons également considérer si l'ajout d'un nouveau paramètre facultatif est possible. Du point de vue des utilisateurs, cela semble être le cas, car le code qui n'utilisait pas ce paramètre auparavant devrait toujours fonctionner avec le nouveau paramètre. Cela exclut donc les commandes comme l'utilisation de l'ordre dans la déclaration ou l'utilisation de l'ordre alphabétique.

Considérez cela également à la lumière du sous-typage et du principe de substitution Liskov - dans la sortie compilée, les mêmes instructions devraient pouvoir invoquer la méthode sur un sous-type avec éventuellement de nouveaux paramètres nommés et sur un supertype.

Implémentations possibles

Si nous ne pouvons pas avoir de commande définitive, nous avons donc besoin d'une structure de données non ordonnée.

  • La mise en œuvre la plus simple consiste à simplement passer le nom des paramètres avec les valeurs. C'est ainsi que les paramètres nommés sont émulés en Perl ou avec des outils de ligne de commande. Cela résout tous les problèmes d'extension mentionnés ci-dessus, mais peut être une énorme perte d'espace - pas une option dans le code critique pour les performances. De plus, le traitement de ces paramètres nommés est maintenant beaucoup plus compliqué que de simplement extraire des valeurs d'une pile.

    En fait, l'espace requis peut être réduit en utilisant le regroupement de chaînes, ce qui peut réduire les comparaisons de chaînes ultérieures aux comparaisons de pointeurs (sauf lorsqu'il ne peut pas être garanti que les chaînes statiques sont réellement regroupées, auquel cas les deux chaînes devront être comparées dans détail).

  • Au lieu de cela, nous pourrions également passer une structure de données intelligente qui fonctionne comme un dictionnaire d'arguments nommés. C'est bon marché du côté de l'appelant, car le jeu de clés est connu statiquement à l'emplacement de l'appel. Cela permettrait de créer une fonction de hachage parfaite ou de précalculer un trie. L'appelé devra toujours tester l'existence de tous les noms de paramètres possibles, ce qui est quelque peu coûteux. Quelque chose comme ça est utilisé par Python.

C'est donc trop cher dans la plupart des cas

Si une fonction avec des paramètres nommés doit être correctement extensible, un ordre définitif ne peut pas être supposé. Il n'y a donc que deux solutions:

  • Intégrez l'ordre des paramètres nommés à la signature et interdisez les modifications ultérieures. Ceci est utile pour le code auto-documenté, mais n'aide pas avec les arguments facultatifs.
  • Passez une structure de données clé-valeur à l'appelé, qui doit ensuite extraire des informations utiles. C'est très cher en comparaison, et généralement vu uniquement dans les langages de script sans accent sur les performances.

Autres pièges

Les noms de variables dans une déclaration de fonction ont généralement une signification interne et ne font pas partie de l'interface - même si de nombreux outils de documentation les affichent toujours. Dans de nombreux cas, vous souhaitez des noms différents pour une variable interne et l'argument nommé correspondant. Les langages qui ne permettent pas de choisir les noms visibles en externe d'un paramètre nommé n'en gagnent pas beaucoup si le nom de variable n'est pas utilisé en tenant compte du contexte d'appel.

Un problème avec les émulations d'arguments nommés est le manque de vérification statique du côté de l'appelant. Ceci est particulièrement facile à oublier lors de la transmission d'un dictionnaire d'arguments (en vous regardant, Python). Ceci est important car le passage d' un dictionnaire est une solution commune, par exemple en JavaScript: foo({bar: "baz", qux: 42}). Ici, ni les types de valeurs ni l'existence ou l'absence de certains noms ne peuvent être vérifiés statiquement.

Émulation de paramètres nommés (dans des langages typés statiquement)

La simple utilisation de chaînes comme clés et de tout objet comme valeur n'est pas très utile en présence d'un vérificateur de type statique. Cependant, les arguments nommés peuvent être émulés avec des structures ou des littéraux d'objet:

// Java

static abstract class Arguments {
  public String bar = "default";
  public int    qux = 0;
}

void foo(Arguments args) {
  ...
}

/* using an initializer block */
foo(new Arguments(){{ bar = "baz"; qux = 42; }});
amon
la source
3
" it is easier to forget the exact names of some parameters than it is to forget their order" ... c'est une affirmation intéressante.
Catskul
1
Le premier contre-exemple n'est pas un problème avec les paramètres nommés, mais plutôt une faille avec la façon dont les pluriels anglais sont mappés aux noms de champs dans les interfaces. Le deuxième contre-exemple est également un problème avec les développeurs qui font de mauvais choix de noms. (Aucun choix d'interface de passage de paramètres ne sera en soi une condition suffisante pour la lisibilité - la question est de savoir si oui ou non c'est une condition nécessaire ou une aide à son égard dans certains cas).
mtraceur
1
+1 pour les points globaux sur les coûts de mise en œuvre et les compromis pour faire les paramètres nommés. Je pense juste que le début perdra des gens.
mtraceur
@mtraceur Vous avez raison. Maintenant, 5 ans plus tard, j'écrirais probablement la réponse très différemment. Si vous le souhaitez, vous pouvez modifier ma réponse pour supprimer les éléments moins utiles. (Habituellement, ces modifications seraient rejetées car elles contredisent l'intention de l'auteur, mais ici je suis d'accord avec cela). Par exemple, je crois maintenant que de nombreux problèmes de paramètres nommés ne sont que des restrictions de langages dynamiques, et ils devraient simplement obtenir un système de type. Par exemple, JavaScript a Flow et TypeScript, Python a MyPy. Avec une intégration IDE appropriée qui élimine la plupart des problèmes de convivialité. J'attends toujours quelque chose en Perl.
2018 à
7

Pour la même raison que la notation hongroise n'est plus largement utilisée; Intellisense (ou son équivalent moral dans les IDE non-Microsoft). La plupart des IDE modernes vous diront tout ce que vous devez savoir sur un paramètre en survolant simplement la référence du paramètre.

Cela dit, un certain nombre de langages prennent en charge les paramètres nommés, notamment C # et Delphi. En C #, il est facultatif, vous n'avez donc pas à l'utiliser si vous ne le souhaitez pas, et il existe d'autres façons de déclarer spécifiquement des membres, comme l'initialisation d'objet.

Les paramètres nommés sont surtout utiles lorsque vous souhaitez uniquement spécifier un sous-ensemble de paramètres facultatifs, et pas tous. En C #, cela est très utile car vous n'avez plus besoin d'un tas de méthodes surchargées pour fournir au programmeur cette flexibilité.

Robert Harvey
la source
4
Python utilise des paramètres nommés, et c'est une merveilleuse fonctionnalité du langage. Je souhaite que plus de langues l'utilisent. Cela rend les arguments par défaut plus faciles car vous n'êtes plus obligé, comme en C ++, de spécifier explicitement les arguments "avant" les valeurs par défaut. (Comme vous l'avez vu dans votre dernier paragraphe, c'est comme ça que python s'en sort sans surcharge ... les arguments par défaut et les arguments de nom vous permettent de faire la même chose.) Les arguments nommés permettent également un code plus lisible. Lorsque vous avez quelque chose comme ça sin(radians=2), vous n'avez pas besoin de "Intellisense".
Gort le robot
2

Bash prend certainement en charge ce style de programmation, et Perl et Ruby prennent en charge la transmission de listes de paramètres de nom / valeur (comme le ferait n'importe quelle langue avec une prise en charge native de hachage / carte). Rien ne vous empêche de choisir de définir une structure / un enregistrement ou un hachage / une carte qui associe des noms aux paramètres.

Pour les langages qui incluent nativement les magasins de hachage / carte / valeur de clé, vous pouvez obtenir la fonctionnalité que vous souhaitez, simplement en adoptant l'idiome pour transmettre des hachages de clé / valeur à vos fonctions / méthodes. Une fois que vous l'avez essayé sur un projet, vous aurez une meilleure idée de savoir si vous avez gagné quelque chose, que ce soit grâce à une productivité accrue découlant de la facilité d'utilisation ou à une qualité améliorée grâce à une réduction des défauts.

Notez que les programmeurs expérimentés sont devenus à l'aise avec le passage des paramètres par ordre / emplacement. Vous pouvez également constater que cet idiome n'est valable que si vous avez plus de quelques arguments (disons> 5/6). Et comme un grand nombre d'arguments indiquent souvent des méthodes (trop) complexes, vous pouvez constater que cet idiome n'est bénéfique que pour les méthodes les plus complexes.

ChuckCottrill
la source
2

Je pense que c'est la même raison pour laquelle c # est plus populaire que VB.net. Alors que VB.NET est plus "lisible", par exemple, vous tapez "fin si" au lieu d'un crochet fermant, cela finit par être plus de choses qui remplissent le code et le rendent finalement plus difficile à comprendre.

Il s'avère que ce qui rend le code plus facile à comprendre est la concision. Moins c'est mieux. Les noms des paramètres de fonction sont normalement assez évidents de toute façon, et ne vont pas vraiment loin pour vous aider à comprendre le code.

Rocklan
la source
1
Je suis en désaccord avec véhémence sur le fait que "moins c'est mieux". Pris à son extrême logique, les gagnants de golf à code seraient les «meilleurs» programmes.
Riley Major
2

Les paramètres nommés sont une solution à un problème de refactorisation du code source et ne visent pas à rendre le code source plus lisible . Les paramètres nommés sont utilisés pour aider le compilateur / analyseur à résoudre les valeurs par défaut des appels de fonction. Si vous voulez rendre le code plus lisible, ajoutez des commentaires significatifs.

Les langages difficiles à refactoriser prennent souvent en charge les paramètres nommés, car ils foo(1)se cassent lors de la signature des foo()modifications, mais foo(name:1)sont moins susceptibles de se rompre, nécessitant moins d'efforts de la part du programmeur pour y apporter des modifications foo.

Que faites-vous lorsque vous devez introduire un nouveau paramètre dans la fonction suivante et qu'il y a des centaines ou des milliers de lignes de code appelant cette fonction?

foo(int weight, int height, int age = 0);

La plupart des programmeurs feront ce qui suit.

foo(int weight, int height, int age = 0, string gender = null);

Désormais, aucune refactorisation n'est requise et la fonction peut s'exécuter en mode hérité lorsqu'elle genderest nulle.

Pour spécifique genderune valeur est maintenant Hardcoded dans l'appel age. Comme cet exemple:

 foo(10,10,0,"female");

Le programmeur a examiné la définition de la fonction, a vu qu'elle ageavait une valeur par défaut 0et a utilisé cette valeur.

Maintenant, la valeur par défaut pour agedans la définition de la fonction est complètement inutile.

Les paramètres nommés vous permettent d'éviter ce problème.

foo(weight: 10, height: 10, gender: "female");

De nouveaux paramètres peuvent être ajoutés fooet vous n'avez pas à vous soucier de l'ordre dans lequel ils ont été ajoutés et vous pouvez modifier les valeurs par défaut en sachant que la valeur par défaut que vous définissez est vraiment sa vraie valeur par défaut.

Reactgular
la source