J'aimerais ajouter quelque chose d'autre qui est suggéré par d'autres réponses, mais je ne pense pas que cela ait été mentionné explicitement:
@puck dit "Il n'y a toujours aucune garantie que le premier argument mentionné dans le nom de la fonction soit réellement le premier paramètre."
@cbojar dit "Utilisez des types au lieu d'arguments ambigus"
Le problème est que les langages de programmation ne comprennent pas les noms: ils sont simplement traités comme des symboles opaques et atomiques. Ainsi, à l'instar des commentaires de code, il n'y a pas nécessairement de corrélation entre le nom d'une fonction et son fonctionnement réel.
Comparez assertExpectedEqualsActual(foo, bar)
avec certaines alternatives (à partir de cette page et ailleurs), comme:
# Putting the arguments in a labelled structure
assertEquals({expected: foo, actual: bar})
# Using a keyword arguments language feature
assertEquals(expected=foo, actual=bar)
# Giving the arguments different types, forcing us to wrap them
assertEquals(Expected(foo), Actual(bar))
# Breaking the symmetry and attaching the code to one of the arguments
bar.Should().Be(foo)
Celles-ci ont toutes plus de structure que le nom commenté, ce qui donne au langage un aspect non opaque à regarder. La définition et l'utilisation de la fonction dépendent également de cette structure. Elle ne peut donc pas être désynchronisée par rapport à ce que fait l'implémentation (comme un nom ou un commentaire).
Lorsque je rencontre ou prévois un problème de ce type, avant de crier sur mon ordinateur de frustration, je prends d'abord un moment pour demander s'il est «juste» de blâmer la machine. En d'autres termes, la machine a-t-elle reçu suffisamment d'informations pour distinguer ce que je voulais de ce que je demandais?
Un appel de ce type a assertEqual(expected, actual)
tout autant de sens que nous assertEqual(actual, expected)
, il est donc facile pour nous de les mélanger et pour que la machine avance et fasse la mauvaise chose. Si nous avons utilisé à la assertExpectedEqualsActual
place, il pourrait faire nous moins susceptibles de faire une erreur, mais il ne donne pas plus d' informations à la machine (il ne peut pas comprendre l' anglais, et le choix du nom ne devrait pas affecter la sémantique).
Ce qui rend les approches "structurées" plus préférables, telles que les arguments de mots clés, les champs libellés, les types distincts, etc., est que les informations supplémentaires sont également lisibles par machine , de sorte que la machine détecte les utilisations incorrectes et nous aide à bien faire les choses. Le assertEqual
cas n’est pas trop grave, le seul problème étant des messages inexacts. Un exemple plus sinistre pourrait être String replace(String old, String new, String content)
, qui est facile à confondre avec String replace(String content, String old, String new)
qui a un sens très différent. Un remède simple serait de prendre une paire [old, new]
, ce qui ferait des erreurs déclencher une erreur immédiatement (même sans types).
Notez que même avec les types, nous pouvons nous retrouver à ne pas "dire à la machine ce que nous voulons". Par exemple, l'anti-motif appelé "programmation très typée" traite toutes les données en tant que chaînes, ce qui permet de mélanger facilement les arguments (comme dans ce cas), d'oublier l'exécution d'une étape (par exemple l'échappement), de casser accidentellement des invariants (par exemple rendant JSON imparable), etc.
Ceci est également lié à la "cécité booléenne", où nous calculons un groupe de booléens (ou de nombres, etc.) dans une partie du code, mais lorsque vous essayez de les utiliser dans une autre, vous ne savez pas exactement ce qu'ils représentent, nous les avons mélangés, etc. Comparez ceci par exemple à des énumérations distinctes qui ont des noms descriptifs (par exemple LOGGING_DISABLED
plutôt que false
) et qui provoquent un message d'erreur si nous les mélangeons.