Duplication de code sans abstraction évidente

14

Avez-vous déjà rencontré un cas de duplication de code où, en regardant les lignes de code, vous ne pouviez pas lui adjoindre une abstraction thématique décrivant fidèlement son rôle dans la logique? Et qu'avez-vous fait pour y remédier?

C'est une duplication de code, donc idéalement, nous devons faire une réfraction, comme par exemple en faire sa propre fonction. Mais comme le code n'a pas une bonne abstraction pour le décrire, le résultat serait une fonction étrange pour laquelle nous ne pouvons même pas trouver un bon nom, et dont le rôle dans la logique n'est pas évident simplement en le regardant. Pour moi, cela nuit à la clarté du code. Nous pouvons conserver la clarté et la laisser telle quelle, mais nous nuisons ensuite la maintenabilité.

Selon vous, quelle est la meilleure façon d'aborder quelque chose comme ça?

EpsilonVector
la source

Réponses:

18

Parfois, la duplication de code est le résultat d'un "jeu de mots": deux choses se ressemblent, mais ne le sont pas.

Il est possible qu'une surabrégé excessif puisse briser la véritable modularité de votre système. Sous le régime de la modularité, vous devez décider "qu'est-ce qui est susceptible de changer?" et "qu'est-ce qui est stable?". Tout ce qui est stable est mis dans l'interface, tandis que tout ce qui est instable est encapsulé dans l'implémentation du module. Ensuite, lorsque les choses changent, la modification que vous devez effectuer est isolée de ce module.

Le refactoring est nécessaire lorsque ce que vous pensiez être stable (par exemple, cet appel API prendra toujours deux arguments) doit changer.

Donc, pour ces deux fragments de code dupliqués, je demanderais: un changement requis pour l'un signifie-t-il nécessairement que l'autre doit également être changé?

La façon dont vous répondez à cette question pourrait vous donner une meilleure idée de ce qu'est une bonne abstraction.

Les modèles de conception sont également des outils utiles. Peut-être que votre code dupliqué effectue une traversée d'une forme quelconque, et le modèle d'itérateur doit être appliqué.

Si votre code dupliqué a plusieurs valeurs de retour (et c'est pourquoi vous ne pouvez pas faire une méthode d'extraction simple), alors vous devriez peut-être créer une classe qui contient les valeurs renvoyées. La classe pourrait appeler une méthode abstraite pour chaque point qui varie entre les deux fragments de code. Vous feriez alors deux implémentations concrètes de la classe: une pour chaque fragment. [Il s'agit en fait du modèle de conception de la méthode de modèle, à ne pas confondre avec le concept de modèles en C ++. Alternativement, ce que vous regardez pourrait être mieux résolu avec le modèle de stratégie.]

Une autre façon naturelle et utile d'y penser est d'utiliser des fonctions d'ordre supérieur. Par exemple, créer des lambdas ou utiliser des classes internes anonymes pour que le code passe à l'abstraction. En général, vous pouvez supprimer la duplication, mais à moins qu'il n'y ait vraiment une relation entre eux [si l'un change, il en va de même pour l'autre], vous risquez de nuire à la modularité, de ne pas l'aider.

Macneil
la source
4

Lorsque vous rencontrez une situation comme celle-ci, il est préférable de penser aux abstractions "non traditionnelles". Peut-être que vous avez beaucoup de duplication au sein d'une fonction et la factorisation d'une ancienne fonction simple ne convient pas très bien car vous devez passer trop de variables. Ici, une fonction imbriquée de style D / Python (avec accès à la portée externe) fonctionnerait très bien. (Oui, vous pouvez créer une classe pour contenir tout cet état, mais si vous ne l'utilisez que dans deux fonctions, c'est une solution de contournement laide et verbeuse pour ne pas avoir de fonctions imbriquées.) Peut-être que l'héritage ne convient pas tout à fait, mais un mixin fonctionnerait bien. Peut-être que vous avez vraiment besoin d'une macro. Vous devriez peut-être envisager une métaprogrammation de modèle ou une réflexion / introspection, ou même une programmation générative.

Bien sûr, d'un point de vue pragmatique, tout cela est difficile, voire impossible, à faire si votre langue ne les prend pas en charge et n'a pas suffisamment de capacités de métaprogrammation pour les implémenter proprement dans la langue. Si tel est le cas, je ne sais pas quoi vous dire sauf "obtenir une meilleure langue". En outre, l'apprentissage d'un langage de haut niveau avec de nombreuses capacités d'abstraction (comme Ruby, Python, Lisp ou D) pourrait vous aider à mieux programmer dans des langages de niveau inférieur où certaines des techniques pourraient encore être utilisables, mais moins évidentes.

dsimcha
la source
+1 pour de nombreuses excellentes techniques compressées dans un espace restreint. (Eh bien, aurait été +1 pour les techniques décrites également.)
Macneil
3

Personnellement, je l'ignore et je continue. Les chances sont que si c'est un cas étrange, il est préférable de le dupliquer, vous pourriez passer des années à refactoriser et le prochain développeur va jeter un coup d'œil et annuler votre changement!

Toby
la source
2

Sans exemple de code, il est difficile de dire pourquoi votre code n'a pas d'abstraction facilement identifiable. Avec cette mise en garde, voici quelques idées:

  • au lieu de créer une nouvelle fonction pour contenir le code commun, divisez la fonctionnalité en plusieurs morceaux distincts;
  • regrouper de petits morceaux en fonction de types de données communs ou d'un comportement abstrait;
  • réécrire le code en double compte tenu des nouvelles pièces;
  • si le nouveau code défie toujours une abstraction claire, divisez-le également en petit et répétez le processus.

La plus grande difficulté de cet exercice est que votre fonction incorpore probablement trop de comportements non liés à un niveau d'abstraction donné, et vous devez gérer certains d'entre eux à des niveaux inférieurs. Vous supposez correctement que la clarté est la clé du maintien du code, mais rendre le comportement du code clair (sa condition actuelle) est très différent de rendre l'intention du code claire.

Faites en sorte que les petits morceaux de code soient abstraits en faisant en sorte que leurs signatures de fonction identifient le quoi, et les gros morceaux devraient être plus faciles à classer.

Huperniketes
la source