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?
Réponses:
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)
oumin(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:
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 .
la source
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 veutColor(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
DateTime
API qui avait deux classes frères pour les points et les durées avec des interfaces presque identiques. BienDateTime->new(...)
accepté unsecond => 30
argument, leDateTime::Duration->new(...)
vouluseconds => 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:
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.
pch
est en fait le caractère de tracé, le glyphe qui sera dessiné pour chaque point de données.17
est un cercle vide, ou quelque chose comme ça.lty
est le type de ligne. Voici1
une ligne continue.bty
est 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:
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:
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:
la source
it is easier to forget the exact names of some parameters than it is to forget their order
" ... c'est une affirmation intéressante.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é.
la source
sin(radians=2)
, vous n'avez pas besoin de "Intellisense".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.
la source
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.
la source
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 desfoo()
modifications, maisfoo(name:1)
sont moins susceptibles de se rompre, nécessitant moins d'efforts de la part du programmeur pour y apporter des modificationsfoo
.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?
La plupart des programmeurs feront ce qui suit.
Désormais, aucune refactorisation n'est requise et la fonction peut s'exécuter en mode hérité lorsqu'elle
gender
est nulle.Pour spécifique
gender
une valeur est maintenant Hardcoded dans l'appelage
. Comme cet exemple:Le programmeur a examiné la définition de la fonction, a vu qu'elle
age
avait une valeur par défaut0
et a utilisé cette valeur.Maintenant, la valeur par défaut pour
age
dans la définition de la fonction est complètement inutile.Les paramètres nommés vous permettent d'éviter ce problème.
De nouveaux paramètres peuvent être ajoutés
foo
et 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.la source