Comment empêcher la duplication de code de manière inconnue?

33

Je travaille sur une base de code assez large. Des centaines de classes, des tonnes de fichiers différents, beaucoup de fonctionnalités, prend plus de 15 minutes pour dérouler une nouvelle copie, etc.

Un gros problème avec une base de code aussi volumineuse est qu'elle a plusieurs méthodes utilitaires et telles qui font la même chose, ou a du code qui n'utilise pas ces méthodes utilitaires quand cela est possible. Et les méthodes utilitaires ne sont pas seulement toutes dans une classe (car ce serait un énorme gâchis).

Je suis assez nouveau dans la base de code, mais le chef d'équipe qui y travaille depuis des années semble avoir le même problème. Cela conduit à beaucoup de code et de duplication de travail, et en tant que tel, quand quelque chose se casse, il est généralement divisé en 4 copies essentiellement du même code

Comment pouvons-nous freiner ce modèle? Comme pour la plupart des grands projets, tout le code n'est pas documenté (même si certains le sont) et tout le code n'est pas ... eh bien, propre. Mais en gros, ce serait vraiment bien si nous pouvions travailler sur l'amélioration de la qualité à cet égard afin qu'à l'avenir nous ayons moins de duplication de code, et des choses comme les fonctions utilitaires soient plus faciles à découvrir.

En outre, les fonctions utilitaires se trouvent généralement dans une classe d'assistance statique, dans une classe d'assistance non statique qui fonctionne sur un seul objet, ou est une méthode statique sur la classe avec laquelle elle "aide" principalement.

J'ai eu une expérience dans l'ajout de fonctions utilitaires en tant que méthodes d'extension (je n'avais pas besoin d'interne de la classe, et cela n'était définitivement requis que dans des scénarios très spécifiques). Cela a eu pour effet d'empêcher d'encombrer la classe primaire et autres, mais ce n'est plus vraiment découvrable à moins que vous ne le sachiez déjà

Earlz
la source

Réponses:

30

La réponse simple est que vous ne pouvez vraiment pas empêcher la duplication de code. Vous pouvez cependant "le réparer" à travers un processus incrémentiel répétitif continu difficile qui se résume en deux étapes:

Étape 1. Commencez à écrire des tests sur le code hérité (de préférence en utilisant un cadre de test)

Étape 2. Réécrivez / refactorisez le code qui est dupliqué en utilisant ce que vous avez appris des tests

Vous pouvez utiliser des outils d'analyse statique pour détecter le code dupliqué et pour C #, il existe de nombreux outils qui peuvent le faire pour vous:

Des outils comme celui-ci vous aideront à trouver des points dans le code qui font des choses similaires. Continuez à écrire des tests pour déterminer qu'ils le font vraiment; utilisez les mêmes tests pour rendre le code en double plus simple à utiliser. Cette «refactorisation» peut être effectuée de plusieurs manières et vous pouvez utiliser cette liste pour déterminer la bonne:

De plus, il y a aussi un livre entier sur ce sujet par Michael C. Feathers, Working Effectively with Legacy Code . Il va en profondeur différentes stratégies que vous pouvez prendre pour changer le code au mieux. Il a un "algorithme de changement de code hérité" qui n'est pas loin du processus en deux étapes ci-dessus:

  1. Identifier les points de changement
  2. Trouver des points de test
  3. Briser les dépendances
  4. Ecrire des tests
  5. Apporter des modifications et refactoriser

Le livre est une bonne lecture si vous avez affaire à un développement sur fond brun, c'est-à-dire un code hérité qui doit changer.

Dans ce cas

Dans le cas de l'OP, je peux imaginer que le code non testable est causé par un pot de miel pour les "méthodes et astuces utilitaires" qui prennent plusieurs formes:

  • méthodes statiques
  • utilisation de ressources statiques
  • cours singleton
  • valeurs magiques

Prenez note qu'il n'y a rien de mal à cela, mais d'un autre côté, ils sont généralement difficiles à entretenir et à modifier. Les méthodes d'extensions dans .NET sont des méthodes statiques, mais sont également relativement faciles à tester.

Avant de procéder aux refactorisations, parlez-en à votre équipe. Ils doivent être conservés sur la même page que vous avant de poursuivre quoi que ce soit. En effet, si vous refactorisez quelque chose, les chances sont élevées, vous provoquerez des conflits de fusion. Donc, avant de retravailler quelque chose, étudiez-le, dites à votre équipe de travailler sur ces points de code avec prudence pendant un certain temps jusqu'à ce que vous ayez terminé.

Étant donné que l'OP est nouveau dans le code, il y a d'autres choses à faire avant de faire quoi que ce soit:

  • Prenez le temps d'apprendre de la base de code, c'est-à-dire rompre "tout", tester "tout", revenir en arrière.
  • Demandez à quelqu'un de l'équipe d'examiner votre code avant de vous engager. ;-)

Bonne chance!

Spoike
la source
Nous avons en fait pas mal de tests unitaires et d'intégration. Pas une couverture à 100%, mais certaines des choses que nous faisons sont presque impossibles à tester unitairement sans modifications radicales de notre base de code. Je n'ai jamais envisagé d'utiliser l'analyse statique pour trouver des doublons. Je vais devoir essayer ça ensuite.
Earlz
@Earlz: L'analyse de code statique est géniale! ;-) Aussi, chaque fois que vous devez faire le changement, pensez à des solutions pour faciliter les changements (consultez le refactoriseur du catalogue de patrons pour cela)
Spoike
+1 Je comprendrais si quelqu'un mettait une prime sur ce Q pour attribuer cette réponse comme "extra utile". Le catalogue Refactor to Patterns est en or, des choses comme celle-ci à la manière de GuidanceExplorer.codeplex.com sont d'excellentes aides à la programmation.
Jeremy Thompson
2

Nous pourrions également essayer de voir le problème sous un autre angle. Au lieu de penser que le problème est la duplication de code, nous pouvons considérer si le problème provient du manque de politiques de réutilisation du code.

J'ai récemment lu le livre Software Engineering with Reusable Components et il contient en effet un ensemble d'idées très intéressantes sur la façon de favoriser la réutilisation du code au niveau de l'organisation.

L'auteur de ce livre, Johannes Sametinger, décrit un ensemble d'obstacles à la réutilisation du code, certains conceptuels, certains techniques. Par exemple:

Conceptuel et technique

  • Difficulté à trouver un logiciel réutilisable : le logiciel ne peut être réutilisé que s'il peut être trouvé. Il est peu probable que la réutilisation se produise lorsqu'un référentiel ne dispose pas d'informations suffisantes sur les composants ou lorsque les composants sont mal classés.
  • Non réutilisabilité des logiciels trouvés : un accès facile aux logiciels existants n'augmente pas nécessairement la réutilisation des logiciels. Par inadvertance, les logiciels sont rarement écrits de manière à ce que d'autres puissent les réutiliser. La modification et l'adaptation du logiciel de quelqu'un d'autre peuvent devenir encore plus coûteuses que la programmation à partir de zéro des fonctionnalités nécessaires.
  • Composants hérités non adaptés à la réutilisation : La réutilisation des composants est difficile ou impossible à moins qu'ils n'aient été conçus et développés pour être réutilisés. Il ne suffit pas de rassembler les composants existants à partir de divers systèmes logiciels hérités et d'essayer de les réutiliser pour de nouveaux développements pour une réutilisation systématique. La réingénierie peut aider à extraire les composants réutilisables, mais l'effort peut être considérable.
  • Technologie orientée objet : il est largement admis que la technologie orientée objet a un impact positif sur la réutilisation des logiciels. Malheureusement et à tort, beaucoup pensent également que la réutilisation dépend de cette technologie ou que l'adoption d'une technologie orientée objet suffit pour la réutilisation de logiciels.
  • Modification : les composants ne seront pas toujours exactement comme nous les voulons. Si des modifications sont nécessaires, nous devrions être en mesure de déterminer leurs effets sur le composant et ses résultats de vérification précédents.
  • Réutilisation des déchets : la certification des composants réutilisables à certains niveaux de qualité permet de minimiser les éventuels défauts. Des contrôles de mauvaise qualité sont l'un des principaux obstacles à la réutilisation. Nous avons besoin de moyens pour juger si les fonctions requises correspondent aux fonctions fournies par un composant.

Les autres difficultés techniques de base comprennent

  • Se mettre d'accord sur ce qu'est un composant réutilisable.
  • Comprendre ce qu'un composant fait et comment l'utiliser.
  • Comprendre comment interfacer des composants réutilisables avec le reste d'une conception.
  • Concevoir des composants réutilisables afin qu'ils soient faciles à adapter et à modifier de manière contrôlée.
  • Organiser un référentiel afin que les programmeurs puissent trouver et utiliser ce dont ils ont besoin.

Selon l'auteur, différents niveaux de réutilisabilité se produisent en fonction de la maturité d'une organisation.

  • Réutilisation ad hoc entre les groupes d'applications : s'il n'y a pas d'engagement explicite de réutilisation, la réutilisation peut se faire au mieux de manière informelle et aléatoire. La plupart de la réutilisation, le cas échéant, se produira au sein des projets. Cela conduit également à la récupération de code et se termine par une duplication de code.
  • Réutilisation basée sur le référentiel parmi les groupes d'applications : la situation s'améliore légèrement lorsqu'un référentiel de composants est utilisé et accessible par différents groupes d'applications. Cependant, aucun mécanisme explicite n'existe pour placer des composants dans le référentiel et personne n'est responsable de la qualité des composants dans le référentiel. Cela peut entraîner de nombreux problèmes et entraver la réutilisation des logiciels.
  • Réutilisation centralisée avec un groupe de composants: Dans ce scénario, un groupe de composants est explicitement responsable du référentiel. Le groupe détermine quels composants doivent être stockés dans le référentiel et garantit la qualité de ces composants et la disponibilité de la documentation nécessaire, et aide à récupérer les composants appropriés dans un scénario de réutilisation particulier. Les groupes d'applications sont séparés du groupe de composants, qui agit comme une sorte de sous-traitant pour chaque groupe d'applications. Un objectif du groupe de composants est de minimiser la redondance. Dans certains modèles, les membres de ce groupe peuvent également travailler sur des projets spécifiques. Pendant le démarrage du projet, leurs connaissances sont précieuses pour favoriser la réutilisation et, grâce à leur implication dans un projet particulier, ils peuvent identifier des candidats potentiels à inclure dans le référentiel.
  • Réutilisation basée sur le domaine : la spécialisation des groupes de composants équivaut à une réutilisation basée sur le domaine. Chaque groupe de domaine est responsable des composants de son domaine, par exemple les composants réseau, les composants d'interface utilisateur, les composants de base de données.

Donc, peut-être, en plus de toutes les suggestions données dans d'autres réponses, vous pourriez travailler sur la conception d'un programme de réutilisabilité, impliquer la gestion, former un groupe de composants responsable de l'identification des composants réutilisables en faisant une analyse de domaine et définir un référentiel de composants réutilisables que d'autres développeurs peuvent facilement interroger et rechercher des solutions cuisinées à leurs problèmes.

edalorzo
la source
1

Il existe 2 solutions possibles:

Prévention - Essayez d'avoir une documentation aussi bonne que possible. Rendez chaque fonction correctement documentée et facile à rechercher dans toute la documentation. En outre, lors de l'écriture de code, indiquez clairement où le code doit aller, il est donc évident où chercher. Limiter la quantité de code "utilitaire" en est l'un des points clés. Chaque fois que j'entends "laisse faire la classe d'utilité", mes cheveux remontent et mon sang gèle, car c'est évidemment un problème. Ayez toujours un moyen rapide et facile de demander aux gens de connaître la base de code chaque fois qu'une fonctionnalité existe déjà.

Solution - Si la prévention échoue, vous devriez être en mesure de résoudre rapidement et efficacement le morceau de code problématique. Votre processus de développement devrait permettre de corriger rapidement le code en double. Les tests unitaires sont parfaits pour cela, car vous pouvez modifier efficacement le code sans craindre de le casser. Donc, si vous trouvez 2 morceaux de code similaires, les résumer dans une fonction ou une classe devrait être facile avec un peu de refactoring.

Personnellement, je ne pense pas que la prévention soit possible. Plus vous essayez, plus il est problématique de trouver des fonctionnalités déjà existantes.

Euphorique
la source
0

Je ne pense pas que ce genre de problèmes ait la solution générale. Le code en double ne sera pas créé si les développeurs sont suffisamment disposés à rechercher le code existant. Les développeurs peuvent également résoudre les problèmes sur place s'ils le souhaitent.

Si le langage est C / C ++, la fusion de duplication sera plus facile en raison de la flexibilité de la liaison (on peut appeler n'importe quelle externfonction sans information préalable). Pour Java ou .NET, vous devrez peut-être concevoir des classes d'assistance et / ou des composants utilitaires.

Je commence généralement la duplication en supprimant le code existant uniquement si les erreurs majeures proviennent des parties dupliquées.

9dan
la source
0

Il s'agit d'un problème typique d'un projet plus vaste qui a été géré par de nombreux programmeurs, qui ont parfois contribué à la pression de leurs pairs. Il est très très tentant de faire une copie d'une classe et de l'adapter à cette classe spécifique. Cependant, lorsqu'un problème a été trouvé dans la classe d'origine, il doit également être résolu dans ses descendants, ce qui est souvent oublié.

Il existe une solution pour cela et elle s'appelle Generics qui a été introduite dans Java 6. C'est l'équivalent de C ++ appelé Template. Code dont la classe exacte n'est pas encore connue dans une classe générique. Veuillez vérifier Java Generics et vous trouverez des tonnes et des tonnes de documentation pour cela.

Une bonne approche consiste à réécrire du code qui semble être copié / collé à de nombreux endroits en réécrivant le premier que vous devez corriger, à cause d'un certain bug. Réécrivez-le pour utiliser des génériques et écrivez également du code de test très rigoureux.

Assurez-vous que chaque méthode de la classe Generic est invoquée. Vous pouvez également introduire des outils de couverture de code: le code générique doit être entièrement couvert par le code car il sera utilisé à plusieurs endroits.

Écrivez également le code de test, c'est-à-dire en utilisant JUnit ou similaire pour la première classe désignée qui va être utilisée conjointement avec le morceau de code générique.

Commencez à utiliser le code générique pour la deuxième version (la plupart du temps) copiée lorsque tout le code précédent fonctionne et est entièrement testé. Vous verrez qu'il existe des lignes de code spécifiques à cette classe désignée. Vous pouvez appeler ces lignes de code dans une méthode protégée abstraite qui doit être implémentée par la classe dérivée qui utilise la classe de base générique.

Oui, c'est un travail fastidieux, mais au fur et à mesure, il sera de mieux en mieux d'extraire des classes similaires et de le remplacer par quelque chose de très très propre, bien écrit et beaucoup plus facile à entretenir.

J'ai eu une situation similaire où une classe générique a finalement remplacé quelque chose comme 6 ou 7 autres classes presque identiques qui étaient presque toutes identiques mais qui ont été copiées et collées par divers programmeurs sur une période de temps.

Et oui, je suis très favorable à un test automatisé du code. Cela coûtera plus cher au début, mais cela vous fera certainement gagner énormément de temps dans l'ensemble. Et essayez d'obtenir une couverture de code globale d'au moins 80% et 100% pour le code générique.

J'espère que cela vous aidera et bonne chance.

André van Kouwen
la source
0

Je vais en fait faire écho à l'opinion la moins populaire ici et à côté Gangnuset suggérer que la duplication de code n'est pas toujours nuisible et pourrait parfois être le moindre mal.

Si, par exemple, vous me donnez la possibilité d'utiliser:

A) Une bibliothèque d'images stable (immuable) et minuscule, bien testée , qui duplique quelques dizaines de lignes de code mathématique trivial pour les mathématiques vectorielles comme les produits scalaires et les lerps et les pinces, mais est complètement découplée de toute autre chose et construit en une fraction de une seconde.

B) Une bibliothèque d'images instable (changeant rapidement) qui dépend d'une bibliothèque mathématique épique pour éviter les quelques dizaines de lignes de code mentionnées ci-dessus, la bibliothèque mathématique étant instable et recevant constamment de nouvelles mises à jour et modifications, et donc la bibliothèque d'images doit également être reconstruit sinon complètement changé aussi. Il faut 15 minutes pour nettoyer le tout.

... alors évidemment, cela devrait être une évidence pour la plupart des gens que A, et en fait précisément en raison de sa duplication de code mineure, est préférable. L'accent clé que je dois faire est la partie bien testée . Évidemment, il n'y a rien de pire que d'avoir du code dupliqué qui ne fonctionne même pas en premier lieu, à ce moment-là, c'est la duplication de bogues.

Mais il y a aussi le couplage et la stabilité à penser, et une duplication modeste ici et là peut servir de mécanisme de découplage qui augmente également la stabilité (nature immuable) du paquet.

Donc, ma suggestion va être en fait de se concentrer davantage sur les tests et d'essayer de trouver quelque chose de vraiment stable (comme en immuable, trouvant peu de raisons de changer à l'avenir) et fiable dont les dépendances aux sources externes, s'il y en a, sont très stable, en essayant d'éliminer toutes les formes de duplication dans votre base de code. Dans un environnement de grande équipe, ce dernier a tendance à être un objectif peu pratique, sans oublier qu'il peut augmenter le couplage et la quantité de code instable que vous avez dans votre base de code.


la source
-2

N'oubliez pas que la duplication de code n'est pas toujours nuisible. Imaginez: vous avez maintenant une tâche à résoudre dans des modules absolument différents de votre projet. En ce moment, c'est la même tâche.

Il peut y avoir trois raisons à cela:

  1. Certains thèmes autour de cette tâche sont les mêmes pour les deux modules. Dans ce cas, la duplication de code est mauvaise et doit être liquidée. Il serait judicieux de créer une classe ou un module pour prendre en charge ce thème et utiliser ses méthodes dans les deux modules.

  2. La tâche est théorique par rapport à votre projet. Par exemple, c'est de la physique ou des mathématiques, etc. La tâche existe indépendamment sur votre projet. Dans ce cas, la duplication de code est mauvaise et doit également être liquidée. Je créerais une classe spéciale pour de telles fonctions. Et utilisez une telle fonction dans n'importe quel module où vous en avez besoin.

  3. Mais dans d'autres cas, la coïncidence des tâches est une coïncidence temporaire et rien de plus. Il serait dangereux de croire que ces tâches resteront les mêmes lors des modifications du projet en raison du refactoring et même du débogage. Dans ce cas, il serait préférable de créer deux mêmes fonctions / morceaux de code à différents endroits. Et les changements futurs dans l'un d'entre eux ne toucheront pas l'autre.

Et ce 3e cas arrive très souvent. Si vous dupliquez "inconsciemment", c'est surtout pour cette raison - ce n'est pas une vraie duplication!

Alors, essayez de le garder propre quand c'est vraiment nécessaire et n'ayez pas peur de la duplication si ce n'est pas le must.

Gangnus
la source
2
code duplication is not always harmfulest un mauvais conseil.
Tulains Córdova
1
Dois-je m'incliner devant votre autorité? J'avais mis mes raisons ici. Si je me trompe, montrez où est l'erreur. Maintenant, il semble plutôt que votre faible capacité à poursuivre la discussion.
Gangnus
3
La duplication de code est l'un des problèmes fondamentaux du développement de logiciels et de nombreux scientifiques et théoriciens de l'informatique ont développé des paradigmes et des méthodologies juste pour éviter la duplication de code comme principale source de problèmes de maintenabilité dans le développement de logiciels. C'est comme dire "écrire un mauvais code n'est pas toujours mauvais", de cette façon, tout peut être justifié rhétoriquement. Peut-être avez-vous raison, mais éviter la duplication de code est un trop bon principe pour encourager le contraire.
Tulains Córdova
Je l' ai mis ici des arguments. Tu ne l'as pas fait. La référence aux autorités ne fonctionnera pas depuis le XVIe siècle. Vous ne pouvez pas garantir que vous les avez bien compris et qu'ils sont aussi des autorités pour moi.
Gangnus
Vous avez raison, la duplication de code n'est pas l' un des problèmes majeurs du développement logiciel, et aucun paradigme ni méthodologie n'a été développé pour l'éviter.
Tulains Córdova