Résolution des problèmes liés à la fonction dyadique assertEquals (attendu, réel)

10

Après des années de codage cowboy, j'ai décidé de prendre un livre sur la façon d'écrire du code de bonne qualité. Je lis Clean Code de Robert Cecil Martin. Dans le chapitre 3 (fonctions), il y a une section sur les fonctions dyadiques. Voici un extrait du livre.

Même les fonctions dyadiques évidentes comme celles-ci assertEquals(expected, actual)sont problématiques. Combien de fois avez-vous mis le réel là où le prévu devrait être? Les deux arguments n'ont pas d'ordre naturel. La commande réelle et attendue est une convention qui requiert de la pratique pour apprendre.

L'auteur fait valoir un argument convaincant. Je travaille dans le machine learning et je le rencontre tout le temps. Par exemple, toutes les fonctions métriques de la bibliothèque sklearn (probablement la bibliothèque python la plus utilisée dans le domaine) vous obligent à faire attention à l'ordre des entrées. Par exemple, sklearn.metrics.homogeneity_score prend comme entrées labels_trueet labels_pred. Ce que fait cette fonction n'est pas trop pertinent, ce qui est pertinent, c'est que si vous changez l'ordre des entrées, aucune erreur ne sera générée. En fait, la commutation des entrées équivaut à utiliser une autre fonction dans la bibliothèque.

Cependant, le livre ne continue pas à dire une solution judicieuse pour des fonctions telles que assertEquals. Je ne peux pas penser à un correctif pour assertEqualsou pour des fonctions que je rencontre souvent comme celle décrite ci-dessus. Quelles sont les bonnes pratiques pour résoudre ce problème?

HBeel
la source

Réponses:

11

Il est bon d'être conscient d'un problème possible même s'il n'y a pas de solution - de cette façon, vous pouvez être vigilant lors de la lecture ou de l'écriture d'un tel code. Dans cet exemple spécifique, vous vous habituez à l'ordre des arguments après un certain temps.

Il existe des moyens au niveau de la langue pour éviter toute confusion concernant l'ordre des paramètres: arguments nommés. Ceci n'est malheureusement pas pris en charge dans de nombreux langages avec une syntaxe de style C comme Java ou C ++. Mais en Python, chaque argument peut être un argument nommé. Au lieu d'appeler une fonction def foo(a, b)as foo(1, 2), nous pouvons le faire foo(a=1, b=2). De nombreux langages modernes comme C # ont une syntaxe similaire. La famille de langage Smalltalk a pris les arguments nommés le plus loin: il n'y a pas d'arguments positionnels et tout est nommé. Cela peut conduire à un code très proche du langage naturel.

Une alternative plus pratique consiste à créer des API qui simulent des arguments nommés. Il peut s'agir d'API fluides ou de fonctions d'assistance qui créent un flux naturel. Le assertEquals(actual, expected)nom prête à confusion. Quelques alternatives que j'ai vues:

  • assertThat(actual, is(equalTo(expected))): en encapsulant certains arguments dans des types d'assistance, les fonctions d'encapsulation servent efficacement de noms de paramètres. Dans le cas spécifique des assertions de tests unitaires, cette technique est utilisée par les évaluateurs Hamcrest pour fournir un système d'assertion extensible et composable. L'inconvénient ici est que vous obtenez beaucoup d'imbrication et que vous devez importer de nombreuses fonctions d'assistance. C'est ma technique de référence en C ++.

  • expect(actual).to.be(expected): une API fluide où vous enchaînez les appels de fonction. Bien que cela évite une imbrication supplémentaire, ce n'est pas très extensible. Bien que je trouve que les API fluentes se lisent très bien, la conception d'une bonne API fluide a tendance à demander beaucoup d'efforts, car vous devez implémenter des classes supplémentaires pour les états non terminaux dans la chaîne d'appel. Cet effort n'est vraiment rentable que dans le contexte d'un IDE à saisie semi-automatique qui peut suggérer les prochains appels de méthode autorisés.

amon
la source
4

Il existe plusieurs méthodes pour éviter ce problème. Celui qui ne vous oblige pas à changer la méthode que vous appelez:

Plutôt que

assertEquals( 42, meaningOfLife() ); 

Utilisation

expected = 42;
actual = meaningOfLife();
assertEquals(expected, actual);

Cela force la convention au grand jour, où il est facile de les voir commuter. Bien sûr, ce n'est pas aussi facile à écrire, mais c'est facile à lire.

Si vous pouvez modifier la méthode appelée, vous pouvez utiliser le système de saisie pour forcer une utilisation facile à lire.

assertThat( meaningOfLife(), is(42) );

Certaines langues vous permettent d'éviter cela car elles ont nommé des paramètres:

assertEquals( expected=42, actual=meaningOfLife() );

D'autres ne le font pas, alors vous les simulez:

assertEquals().expected(42).actual( meaningOfLife() );

Quoi que vous fassiez, trouvez un moyen de le rendre évident qui est correct lors de la lecture. Ne me faites pas deviner quelle est la convention. Montre le moi.

candied_orange
la source