Couplage de code introduit par DRY et OOD

14

Je cherche des conseils sur le couplage DRY vs Code. Je n'aime pas dupliquer mon code et je n'aime pas non plus le couplage de code entre des modules non liés. Je refactorise donc le code en double si je trouve du code identique un an après l'introduction de la duplication. Cependant, j'ai de plus en plus connu des situations où le monde réel est beaucoup plus imprévisible, et après avoir refactorisé le code, il se produit des situations qui nécessitent de refaire le code.

Par exemple, si j'avais un code pour gérer les voitures à essence, les VUS à essence, les voitures électriques et les VUS électriques, disons que j'ai refactorisé le code en double dans la hiérarchie "essence" et la hiérarchie "électrique", toutes deux descendant de la hiérarchie "véhicule". Jusqu'ici tout va bien. Et puis, mon entreprise présente une voiture hybride et une semi-hybride qui nécessiteraient des changements de base dans ma hiérarchie d'origine elle-même. Il faudrait peut-être une "composition" entre l'essence et les hiérarchies électriques.

Il est clair que la duplication de code est mauvaise car elle augmente le temps nécessaire pour implémenter un changement commun à tous les produits ci-dessus. Mais la refactorisation du code commun rend également difficile l'introduction de variations spécifiques au produit, et conduit à beaucoup de "saut de classe" quand il faut trouver la ligne de code pour corriger un bogue - un changement dans une classe parent de niveau supérieur pourrait déclencher déclencher des bogues de régression parmi tous les descendants.

Comment trouver un équilibre optimal entre DRY et couplage de code indésirable?

user2549686
la source
1
Il est toujours bon de ne pas suivre les règles à l'aveuglette mais d'engager d'abord le cerveau.
gnasher729
assez juste - donc des lignes directrices, pas des règles.
user2549686
Avez-vous lu la page Wiki sur DRY (ou OAOO, comme on l'appelait auparavant)? wiki.c2.com/?OnceAndOnlyOnce C'est que s'est déroulée une grande partie de la discussion initiale à ce sujet. (En fait, Ward a inventé le Wiki spécifiquement pour les discussions sur les modèles et les pratiques.)
Jörg W Mittag
Réponses très liées, même si la question ne ressemble pas tout à fait à un doublon: softwareengineering.stackexchange.com/questions/300043/…
Hulk

Réponses:

8

disons que j'ai refactorisé le code en double dans la hiérarchie "essence" et la hiérarchie "électrique", toutes deux descendant de la hiérarchie "véhicule". Jusqu'ici tout va bien.

Et puis, mon entreprise présente une voiture hybride et une semi-hybride qui nécessiteraient des changements de base dans ma hiérarchie d'origine elle-même. Il faudrait peut-être une "composition" entre l'essence et les hiérarchies électriques

Je pense que c'est l'une des principales raisons pour lesquelles les gens s'orientent vers la composition plutôt que l'héritage.

L'héritage vous oblige à une grande restructuration lorsque vous avez un changement conceptuel comme celui que vous décrivez.

Lorsque la restructuration est «trop importante / difficile», les gens écrivent du code en double pour l'éviter.

Plutôt que d'éloigner le doublon en déplaçant le code vers le haut de la chaîne d'héritage, vous pouvez le déplacer vers une classe ou un service d'assistance, puis injecter cette classe dans une composition si nécessaire.

La question de savoir si la conception résultante est OOP est sujette à débat

Ewan
la source
1
+1 pour avoir souligné que l'héritage est le problème, pas SEC. La façon la plus simple de réutiliser le code est de supprimer les dépendances inutiles, et trop souvent, les gens oublient le fait qu'une classe parent (et ses descendants) sont toutes des dépendances lors de l'utilisation de l'héritage. Ce n'est que lorsque vous réduisez ou supprimez les dépendances que vous obtenez réellement du code réutilisable.
Greg Burghardt
3
"La question de savoir si le design résultant est OOP est sujette à débat ". Tout est ouvert au débat. La question à se poser est la suivante: est-il ouvert à un débat raisonnable et rationnel? La réponse est non. Je pense que votre dernier paragraphe nuit à une très bonne réponse.
David Arno
@davidarno ne vous suit pas vraiment, j'ai lu OOD dans le titre comme Design Orienté Objet? donc je m'attaque à la question sans chaluter dans le débat
Ewan
1
@Ewan: Je suis ici avec David Arno. Je pense qu'il est bien connu depuis plus de 2 décennies que croire "s'il n'y a pas d'héritage, alors ce n'est pas OOP" est une erreur.
Doc Brown
jeeze c'est exactement le genre de discussion que j'essayais d'éviter. il y a des vues des deux côtés, il n'y a pas de réponse définitive
Ewan
3

Vous avez raison, suivant le principe DRY, vous pouvez augmenter le couplage entre des modules qui ne sont pas liés. Surtout dans les grands systèmes logiciels, cela peut conduire à des situations où ne pas suivre DRY peut être la meilleure alternative.

Malheureusement, votre exemple n'est pas bien adapté pour le démontrer - les problèmes décrits ici sont causés par des erreurs classiques dans une mauvaise utilisation sous-optimale de l'héritage. Cependant, pour ce que j'ai écrit ci-dessus, peu importe que vous refactoriez le code commun en une classe de base commune ou en classe d'assistance (composition). Dans les deux cas, on peut être contraint de mettre le code commun dans une bibliothèque L et de référencer cette bibliothèque à partir de deux programmes auparavant indépendants A et B.

Supposons que A et B étaient complètement indépendants auparavant, ils peuvent être versionnés, publiés et déployés indépendamment. Cependant, en plaçant du code commun dans une bibliothèque L partagée, de nouvelles exigences pour A peuvent induire des modifications dans L, ce qui peut maintenant entraîner des modifications dans B. Cela entraîne donc la nécessité de tests supplémentaires, et probablement une nouvelle version et un nouveau cycle de déploiement pour B.

Alors, comment pouvez-vous gérer cette situation, si vous n'êtes pas prêt à abandonner le principe DRY? Eh bien, il existe des tactiques bien connues pour aborder cela:

  1. Conservez A, B et L dans le même produit, avec un numéro de version commun, un processus de génération, de publication et de déploiement commun, avec un degré élevé d'automatisation

  2. ou faire de L un produit à lui seul, avec des numéros de version mineurs (pas de modifications incompatibles) et des numéros de version majeurs (contenant peut-être des changements de rupture), et laisser A et B autoriser chacun à référencer une ligne de version différente de L.

  3. Rendez L aussi SOLIDE que possible et veillez à la compatibilité descendante. Plus il y a de modules dans L pouvant être réutilisés sans modification (OCP), moins les changements de cassure se produiront Et les autres principes de "SOLID" contribuent à soutenir cet objectif.

  4. Utilisez des tests automatiques en particulier pour L, mais aussi pour A et B.

  5. Faites attention à ce que vous mettez dans L. La logique métier qui ne devrait exister qu'à un seul endroit dans un système est un bon candidat. Les choses qui «se ressemblent» et qui pourraient varier différemment à l'avenir sont de mauvais candidats.

Notez que lorsque A et B sont développés, maintenus et évolués par des équipes différentes et non liées, le principe DRY devient beaucoup moins important - DRY concerne la maintenabilité et l'évolutivité, mais laisser deux équipes différentes fournir un effort de maintenance individuel peut être parfois plus efficace que de lier leurs produits ensemble à cause d'un peu de réutilisation.

Donc en fin de compte, c'est un compromis. Si vous souhaitez suivre le principe DRY dans des systèmes plus grands, vous devez investir beaucoup plus d'efforts dans la création de composants robustes et réutilisables - généralement plus que ce que vous attendez. Vous devez faire confiance à votre jugement quand cela en vaut la peine et quand ce n'est pas le cas.

Doc Brown
la source
1

SEC est encore une autre règle structurelle. Cela signifie que si vous le prenez à l'extrême, c'est aussi mauvais que si vous l'ignorez. Voici une métaphore pour vous sortir de la mentalité structurelle.

Aimez vos jumeaux. Tuez les clones.

Lorsque vous copiez et collez aveuglément pour éviter de taper au clavier, vous créez des clones sans réfléchir. Bien sûr, ils font ce que vous voulez, mais ils vous alourdissent parce que changer ce comportement coûte tellement cher.

Lorsque vous reproduisez un comportement identique avec un code identique en raison d'une responsabilité différente, vous avez le même besoin, mais cela peut vous soumettre à des changements différents, vous avez un jumeau qui devrait être libre de changer et de grandir en tant qu'individu au fil du temps.

Vous pouvez scanner leur ADN (code) autant que vous le souhaitez. Les clones et les jumeaux sont difficiles à distinguer si tout ce que vous faites est de les regarder. Ce dont vous avez besoin, c'est de comprendre le contexte dans lequel ils vivent. Pourquoi ils sont nés. Quel sera leur sort ultime.

Disons que vous travaillez pour la société ABC. Il est géré par trois dirigeants d'entreprise, A, B et C. Tous veulent que vous fabriquiez un produit logiciel, mais ils ont chacun leurs propres services. Ils veulent tous que vous commenciez petit. Sortez simplement un message simple pour l'instant. Vous écrivez donc "Hello World".

A déteste ça, veut que vous y mettiez le nom de l'entreprise.

B adore ça, veut que vous le laissiez tranquille et que vous ajoutiez le nom de l'entreprise à un écran de démarrage.

C veut juste une calculatrice et pensait que le message n'était qu'un moyen pour vous de commencer.

Une chose dont vous êtes sûr, ces gars-là ont des idées très différentes de ce qu'est ce projet. Parfois, ils vont être d'accord. Parfois, ils vont vous entraîner dans des directions différentes.

Lorsque vous dupliquez du code parce que vous créez la possibilité pour ce code de varier indépendamment, vous créez un jumeau. Lorsque vous dupliquez du code parce que la saisie est difficile et que le copier-coller est facile, vous créez des clones maléfiques.

A, B et C sont des maîtres différents que votre code doit servir. Aucune ligne de code ne peut servir plus d'un maître pendant longtemps.

candied_orange
la source
Tout d'abord, merci à tous pour tous les commentaires.
user2549686