Duplication de code illusoire

56

L'instinct habituel est de supprimer toute duplication de code que vous voyez dans le code. Cependant, je me suis retrouvé dans une situation où la duplication est illusoire .

Pour décrire la situation plus en détail: Je développe une application Web et la plupart des vues sont fondamentalement les mêmes. Elles affichent une liste d’éléments dans lesquels l’utilisateur peut faire défiler et choisir, une seconde liste contenant les éléments "bouton pour enregistrer la nouvelle liste.

Il m'a semblé que le problème est facile. Cependant, chaque vue a ses propres particularités - parfois, vous devez recalculer quelque chose, parfois vous devez stocker des données supplémentaires, etc. Cela a été résolu en insérant des points d'ancrage dans le code logique principal.

Les différences entre les vues sont si nombreuses qu'il devient de moins en moins facile à gérer, car je dois fournir des rappels pour pratiquement toutes les fonctionnalités, et la logique principale commence à ressembler à une énorme séquence d'appels de rappel. En fin de compte, je ne gagne pas de temps ni de code, car chaque vue a son propre code exécuté - le tout en rappel.

Les problèmes sont:

  • les différences sont si minimes que le code est presque identique à tous les points de vue,
  • il y a tellement de différences que lorsque vous regardez les détails, coder n'est pas un peu pareil

Comment dois-je gérer cette situation?
Avoir une logique de base entièrement composée d'appels de rappel est-il une bonne solution?
Ou devrais-je plutôt dupliquer le code et abandonner la complexité du code basé sur le rappel?

Mael
la source
38
Je trouve généralement utile de laisser la duplication disparaître initialement. Une fois que j'ai quelques exemples, il est beaucoup plus facile de voir ce qui est commun et ce qui ne l’est pas et de trouver un moyen de partager les parties communes.
Winston Ewert
7
Très bonne question - une chose à considérer n'est pas seulement la duplication physique du code mais la duplication sémantique. Si un changement dans une partie du code signifie nécessairement que le même changement serait dupliqué dans les autres, alors cette partie est probablement un candidat à la refactorisation ou à l'abstraction. Parfois, vous pouvez normaliser au point de vous piéger vous-même. Je considérerais donc également les implications pratiques pour traiter la duplication comme sémantiquement distincte - elles peuvent être compensées par les conséquences d'essayer de dédupliquer.
Ant P
N'oubliez pas que vous ne pouvez réutiliser que du code faisant la même chose. Si votre application fait différentes choses sur différents écrans, des rappels différents seront nécessaires. Pas de si, ni de mais, à ce sujet.
CorsiKa
13
Ma règle personnelle est la suivante: si je modifie le code à un endroit, est-ce un bug si je ne fais pas exactement la même modification partout ailleurs? Si c'est le cas, c'est le mauvais type de duplication. Si je ne suis pas sûr, choisissez celui qui est le plus lisible pour le moment. Dans votre exemple, les différences de comportement sont intentionnelles et ne sont pas considérées comme des bogues. Par conséquent, certaines duplications sont acceptables.
Ixrec
Vous pourriez être intéressé à lire sur la programmation orientée aspect.
Ben Jackson

Réponses:

53

En fin de compte, vous devez vous prononcer sur la possibilité de combiner un code similaire pour éliminer les doublons.

Il semble exister une fâcheuse tendance à considérer les principes du type "Ne vous répétez pas" comme des règles à suivre systématiquement. En fait, ce ne sont pas des règles universelles, mais des directives qui devraient vous aider à réfléchir et à développer un bon design.

Comme tout dans la vie, vous devez considérer les avantages par rapport aux coûts. Combien de code dupliqué sera supprimé? Combien de fois le code est répété? Combien d'effort faudra-t-il pour écrire un design plus générique? À quel point êtes-vous susceptible de développer le code dans le futur? Etc.

Sans connaître votre code spécifique, ceci n'est pas clair. Il existe peut-être un moyen plus élégant d’éliminer les doubles emplois (comme celui suggéré par LindaJeanne). Ou peut-être qu'il n'y a tout simplement pas assez de répétition vraie pour justifier une abstraction.

Une attention insuffisante au design est un piège, mais il faut aussi se méfier du sur-design.


la source
Je pense que votre commentaire sur les "tendances malheureuses" et sur le respect aveugle des directives est sur place.
Mael
1
@Mael Êtes-vous en train de dire que si vous ne mainteniez pas ce code à l'avenir, vous n'auriez pas de bonnes raisons d'obtenir le bon design? (aucune infraction, je veux juste savoir ce que vous en pensez)
Repéré
2
@Mael Bien sûr, nous pourrions considérer cela comme une tournure de phrase malheureuse! : D Cependant, je pense que nous devrions être aussi stricts avec nous-mêmes que nous le sommes pour les autres lors de l'écriture de code (je me considère comme un autre lorsque je lis mon propre code deux semaines après l'avoir écrit).
Repéré
2
@ user61852 alors vous n'aimeriez pas beaucoup le code sans code .
RubberDuck
1
@ user61852, haha - mais si ça ne dépendent tous (des informations non dans la question)? Peu de choses sont moins utiles qu'un excès de certitude.
43

Rappelez-vous que DRY concerne la connaissance . Peu importe que deux éléments de code soient similaires, identiques ou totalement différents. Ce qui compte, c'est si le même élément de connaissance sur votre système peut être trouvé dans les deux éléments.

Une connaissance peut être un fait ("l'écart maximal autorisé par rapport à la valeur souhaitée est de 0,1%") ou un aspect de votre processus ("cette file d'attente ne contient jamais plus de trois éléments"). Il s’agit essentiellement d’une information encodée dans votre code source.

Ainsi, lorsque vous décidez si une duplication doit être supprimée, demandez-lui s'il s'agit d'une duplication de connaissances. Si ce n'est pas le cas, il s'agit probablement d'une duplication accidentelle et son extraction à un endroit commun risque de poser des problèmes si vous souhaitez créer ultérieurement un composant similaire lorsque cette partie apparemment dupliquée est différente.

Ben Aaronson
la source
12
Cette! DRY vise à éviter les modifications en double .
Matthieu M.
Ceci est très utile.
1
Je pensais que l'objectif de DRY était de faire en sorte qu'il n'y ait pas deux morceaux de code qui devraient se comporter de manière identique, mais ne le font pas. Le problème n'est pas le travail doublé, car les modifications de code doivent être appliquées deux fois. Le vrai problème est lorsqu'un changement de code doit être appliqué deux fois mais ne l'est pas.
gnasher729
3
@ gnasher729 Oui, c'est le but. Si deux éléments de code présentent des doublons de connaissances , vous vous attendez à ce que l'un d'eux change, alors que l'autre doit également changer, ce qui entraîne le problème que vous décrivez. En cas de duplication accidentelle , alors, lorsque l’une doit changer, l’autre peut devoir rester la même. Dans ce cas, si vous avez extrait une méthode commune (ou autre), vous avez maintenant un problème différent à traiter
Ben Aaronson
1
Également pour la duplication essentielle et la duplication accidentelle , reportez-vous à la section Doppelgänger accidentel dans Ruby et moi , j'ai séché mon code et c'est maintenant difficile de travailler avec. Qu'est-il arrivé? . Des doublons accidentels se produisent également des deux côtés d'une limite de contexte . Résumé: ne fusionnez les doublons que s'il est logique pour leurs clients que ces dépendances soient modifiées simultanément .
Eric
27

Avez-vous envisagé d'utiliser un modèle de stratégie ? Vous auriez une classe View contenant le code commun et les routines appelées par plusieurs vues. Les enfants de la classe View contiendraient le code spécifique à ces instances. Ils utiliseraient tous l'interface commune que vous avez créée pour la vue. Ainsi, les différences seraient encapsulées et cohérentes.

LindaJeanne
la source
5
Non, je n'y ai pas pensé. Merci pour la suggestion. Une rapide lecture sur le modèle de stratégie semble être quelque chose que je recherche. Je vais certainement enquêter plus loin.
Mael
3
il existe un modèle de méthode de modèle . Vous pouvez également considérer cela
Shakil
5

Quel est le potentiel de changement? Par exemple, notre application dispose de 8 domaines d’activité différents avec un potentiel de 4 types d’utilisateurs ou plus pour chaque domaine. Les vues sont personnalisées en fonction du type d'utilisateur et de la zone.

Initialement, cela a été fait en utilisant la même vue avec quelques vérifications ici et là pour déterminer si différentes choses devraient apparaître. Au fil du temps, certains secteurs d’activité ont décidé de prendre des mesures radicalement différentes. Au final, nous avons essentiellement migré vers une vue (vues partielles, dans le cas d’ASP.NET MVC) par fonctionnalité et par domaine d’activité. Tous les domaines d’activité n’ont pas la même fonctionnalité, mais si l’on souhaite une fonctionnalité identique à celle d’un autre, cette zone obtient son propre point de vue. C'est beaucoup moins lourd pour la compréhension du code, ainsi que pour la testabilité. Par exemple, faire un changement pour un domaine ne provoquera pas de changement indésirable pour un autre domaine.

Comme @ dan1111 l'a mentionné, il peut s'agir d'un jugement. Avec le temps, vous constaterez peut-être que cela fonctionne ou non.

ps2goat
la source
2

L’un des problèmes peut être que vous fournissez une interface (interface théorique, et non une fonctionnalité linguistique) à un seul niveau de fonctionnalité:

A(a,b,c) //a,b,c are your callbacks or other dependencies

Au lieu de plusieurs niveaux selon le degré de contrôle requis:

//high level
A(a,b,c)
//lower
A myA(a,b)
B(myA,c)
//even lower
A myA(a)
B myB(myA,b)
C myC(myB,c)
//all the way down to you just having to write the code yourself

D'après ce que j'ai compris, vous n'exposez que l'interface de haut niveau (A), en masquant les détails de la mise en œuvre (les autres éléments présents).

Masquer les détails de la mise en œuvre présente des avantages, et vous venez de constater un inconvénient: le contrôle est limité, à moins que vous ajoutiez explicitement des fonctionnalités pour chaque chose qui aurait été possible si vous utilisiez directement les interfaces de bas niveau.

Donc, vous avez deux options. Soit vous utilisez uniquement l'interface de bas niveau, vous utilisez l'interface de bas niveau car l'interface de haut niveau demandait trop de travail à maintenir, ou vous exposiez des interfaces de niveau haut et bas. La seule option judicieuse consiste à offrir des interfaces de haut niveau et de bas niveau (et tout ce qui se trouve entre les deux), en supposant que vous souhaitiez éviter un code redondant.

Ensuite, lorsque vous écrivez une autre de vos choses, vous regardez toutes les fonctionnalités disponibles que vous avez écrites jusque-là (d'innombrables possibilités, à vous de décider lesquelles seront réutilisées) et de les assembler.

Utilisez un seul objet où vous avez besoin de peu de contrôle.

Utilisez la fonctionnalité de niveau le plus bas lorsque l’étranger doit se produire.

Ce n'est pas non plus très noir et blanc. Peut-être que votre grande classe de haut niveau PEUT raisonnablement couvrir tous les cas d'utilisation possibles. Peut-être que les cas d'utilisation varient tellement que rien, à part la fonctionnalité primitive de niveau le plus bas, ne suffit. A vous de trouver l'équilibre.

Waterlimon
la source
1

Il y a déjà d'autres réponses utiles. Je vais ajouter le mien.

La duplication est mauvaise parce que

  1. ça encombre le code
  2. il encombre notre composition du code, mais surtout
  3. parce que si vous changez quelque chose ici et que vous devez également changer quelque chose - bas , vous pourriez oublier / introduire des bugs / ... et il est difficile de ne jamais oublier.

Donc, le fait est que vous n'éliminez pas le double emploi pour le plaisir ou parce que quelqu'un a dit que c'était important. Vous le faites parce que vous voulez réduire les bugs / problèmes. Dans votre cas, il semble que si vous modifiez quelque chose dans une vue, vous n'aurez probablement pas besoin de changer exactement la même ligne dans toutes les autres vues. Donc, vous avez une duplication apparente , pas une duplication réelle.

Un autre point important est de ne jamais réécrire à partir de zéro quelque chose qui fonctionne à présent uniquement sur la base d'une question de principe, comme l'a dit Joel (vous auriez déjà entendu parler de lui ...). Donc, si vos points de vue fonctionnent, continuez à vous améliorer étape par étape et ne tombez pas dans le piège de la "pire erreur stratégique qu'une société de logiciels puisse commettre".

Francesco
la source