Comment supprimer une fonction ou une fonctionnalité lors de l'utilisation de TDD

20

Dans les textes sur TDD, je lis souvent sur «supprimer la duplication» ou «améliorer la lisibilité» lors de l'étape de refactoring. Mais qu'est-ce qui me fait supprimer une fonction inutilisée?

Par exemple, supposons qu'il existe une classe Cavec des méthodes a()et b(). Maintenant, je pense que ce serait bien d'avoir une méthode f()qui est poussée C. En fait, f()remplace tous les appels à, b()à l'exception des tests unitaires définis / décrits b(). Ce n'est plus nécessaire - sauf pour les tests.

Est-il sauvegardé pour simplement supprimer b()et tous les tests qui l'ont utilisé? Cela fait-il partie de "améliorer la lisibilité"?

TobiMcNamobi
la source
3
Ajoutez simplement un autre test pour vérifier que la fonction n'existe pas, puis corrigez les cas de test défaillants;)
Filip Haglund
@FilipHaglund Selon la langue, cela peut être possible. Mais en tant que documentation de code, cela semblerait étrange.
TobiMcNamobi
1
Pour tous ceux qui ne connaissent pas mieux: le commentaire de FilipHaglund est évidemment une blague. Ne fais pas ça.
jhyot

Réponses:

16

Oui bien sûr. Le code le plus facile à lire est celui qui n'est pas là.

Cela dit, le refactoring signifie généralement améliorer le code sans changer son comportement. Si vous pensez à quelque chose qui améliore le code, faites-le. Il n'est pas nécessaire de l'installer dans un trou de pigeon avant de pouvoir le faire.

Sebastian Redl
la source
@ jpmc26 Agile, man, agile! :-)
TobiMcNamobi
1
"Le code le plus facile à lire est celui qui n'est pas là." -
J'accroche
27

La suppression d'une méthode publique n'est pas une «refactorisation» - la refactorisation modifie l'implémentation tout en continuant à passer les tests existants.

Cependant, la suppression d'une méthode inutile est un changement de conception parfaitement raisonnable.

TDD tire cela dans une certaine mesure, car en examinant les tests, vous pouvez remarquer qu'il teste une méthode inutile. Les tests pilotent votre conception, car vous pouvez aller "Regardez, ce test n'a rien à voir avec mon objectif".

Il peut se révéler davantage à des niveaux de test plus élevés, en conjonction avec des outils de couverture de code. Si vous exécutez des tests d'intégration avec une couverture de code et voyez que les méthodes ne sont pas appelées, c'est un indice qu'une méthode n'est pas utilisée. L'analyse de code statique peut également indiquer que les méthodes ne sont pas utilisées.

Il existe deux approches pour supprimer une méthode; les deux travaillent dans des circonstances différentes:

  1. Supprimez la méthode. Suivez les erreurs de compilation pour supprimer le code et les tests dépendants. Si vous êtes convaincu que les tests concernés sont jetables, validez vos modifications. Sinon, reculez.

  2. Supprimez les tests que vous jugez obsolètes. Exécutez l'ensemble de votre suite de tests avec une couverture de code. Supprimez les méthodes qui n'ont pas été exercées par la suite de tests.

(Cela présuppose que votre suite de tests dispose d'une bonne couverture pour commencer).

svelte
la source
10

En fait, f () remplace tous les appels à b () à l'exception des tests unitaires qui ont défini / décrit b ()

À mon humble avis, le cycle TDD typique ressemblera à ceci:

  • écrire des tests qui échouent pour f () (probablement basé sur les tests pour b ()): les tests deviennent rouges

  • implémenter f () -> les tests deviennent verts

  • refactor : -> supprimer b () et tous les tests pour b ()

Pour la dernière étape, vous pourriez envisager de supprimer b () en premier et de voir ce qui se passe (lors de l'utilisation d'un langage compilé, le compilateur ne doit se plaindre que des tests existants, sinon, les anciens tests unitaires pour b échoueront, il est donc vous devez également les supprimer).

Doc Brown
la source
4

Oui, ça l'est.

Le meilleur code, le plus exempt de bogues et le plus lisible est le code qui n'existe pas. Efforcez-vous d'écrire autant de non-code que possible tout en répondant à vos exigences.

Kilian Foth
la source
9
Vous avez manqué la partie "comment faire".
JeffO
2

Il est souhaitable de supprimer b()une fois qu'il n'est plus utilisé, pour la même raison qu'il est souhaitable de ne pas ajouter de fonctions inutilisées en premier lieu. Que vous l'appeliez "lisibilité" ou autre chose, toutes choses étant égales par ailleurs, c'est une amélioration du code qu'il ne contient rien pour lequel il ne sert à rien. Pour avoir au moins une mesure spécifique par laquelle il vaut mieux ne pas l'avoir, sa suppression garantit que son coût de maintenance futur après ce changement est nul!

Je n'ai trouvé aucune technique spéciale pour le supprimer avec ses tests, car toute idée de remplacement b()par quelque chose de nouveau doit bien sûr être accompagnée d'une considération de tout le code en cours d'appel b(), et les tests sont un sous-ensemble de "tout le code" ".

Le raisonnement qui fonctionne généralement pour moi est qu'au moment où je remarque que cela f()est devenu b()obsolète, il b()devrait donc être au moins obsolète, et je cherche à trouver tous les appels à b()dans l'intention de les remplacer par des appels à f(), je considérez également le code de test . Plus précisément, si ce b()n'est plus nécessaire, je peux et je dois supprimer ses tests unitaires.

Vous avez tout à fait raison de dire que rien ne m'oblige à remarquer que ce b()n'est plus nécessaire. C'est une question de compétence (et, comme le dit Slim, les rapports de couverture de code sur les tests de niveau supérieur). Si seuls des tests unitaires, et aucun test fonctionnel, se réfèrent à b(), alors je peux être prudemment optimiste que cela ne fait partie d'aucune interface publiée et donc le supprimer n'est pas un changement de rupture pour tout code qui n'est pas sous mon contrôle direct.

Le cycle rouge / vert / refactor ne mentionne pas explicitement la suppression des tests. En outre, la suppression b()viole le principe ouvert / fermé car il est clair que votre composant est ouvert à modification. Donc, si vous voulez considérer cette étape comme quelque chose en dehors du simple TDD, allez-y. Par exemple, vous pouvez avoir un processus pour déclarer un test "mauvais", qui peut être appliqué dans ce cas pour supprimer le test au motif qu'il teste quelque chose qui ne devrait pas être là (la fonction inutile b()).

Je pense que dans la pratique, la plupart des gens autorisent probablement une certaine refonte avec un cycle rouge / vert / refactor, ou ils considèrent la suppression des tests unitaires redondants comme une partie valide d'un "refactor", même si à proprement parler ce n'est pas du refactoring. Votre équipe peut décider de la quantité de pièces de théâtre et de paperasse à impliquer pour justifier cette décision.

Quoi qu'il en soit, si b()c'était important, il y avait des tests fonctionnels pour cela, et ceux-ci ne seraient pas supprimés à la légère, mais vous avez déjà dit qu'il n'y avait que des tests unitaires. Si vous ne faites pas correctement la distinction entre les tests unitaires (écrits sur la conception interne actuelle du code, que vous avez modifiée) et les tests fonctionnels (écrits sur des interfaces publiées, que vous ne voulez peut-être pas modifier), alors vous devez être plus prudent. sur la suppression des tests unitaires.

Steve Jessop
la source
2

Une chose dont il faut toujours se souvenir est que nous utilisons maintenant les REPOSITOIRES DE CODE avec VERSION CONTROL. Ce code supprimé n'est pas réellement parti ... il est toujours là quelque part dans une itération précédente. Alors souffle-le! Soyez libéral avec la touche Suppr, car vous pouvez toujours revenir en arrière et récupérer cette précieuse méthode élégante que vous pensiez être utile un jour ... si jamais un jour venait. C'est là.

Bien sûr, cela va de pair avec la mise en garde des maux et du danger des versions non rétrocompatibles ... des applications externes qui reposaient sur la mise en œuvre de votre interface, qui sont désormais orphelines par votre code (soudainement) obsolète.

dwoz
la source
La suppression de code n'est pas un problème en général. J'aime supprimer des lignes de suppression, les fonctions, ou ... des modules bien, entiers! Si possible. Et je fais tout cela avec ou sans TDD. Mais: y a-t-il un point dans le flux de travail TDD où le code (non-dupli) est supprimé? Sinon, il sera supprimé de toute façon. Mais y en a-t-il? C'était ma question.
TobiMcNamobi