Ne vous répétez pas ! JAMAIS !!


Les racines du mal

Dette technique, à force, ce terme devrait vous être familier. C’est le phénomène qui se produit lorsqu’une codebase prend la poussière, et qui ravage chaque jour des milliers de projets, vide les tirelires, fait hurler les clients et déprimer les développeurs (surtout moi).

Mais en soi, la dette technique n’est pas « réelle », ce n’est qu’un concept qui se manifeste sous plusieurs formes plus ou moins évidentes.

Mauvaise lisibilité du code, perte de flexibilité, et bien sûr, la plus odieuse de toutes, énormément de duplication.

J’en ai souvent parlé dans mes articles en disant que c’est le mal incarné, et qu’il ne fallait jamais y céder. Mais est-ce vraiment le cas, cher ami ?

Bah, un p’tit copier-coller de temps en temps, ça ne fait de mal à personne.

Surtout pas malheureux ! La duplication n’est pas qu’une simple mauvaise pratique, c’est la pire de toutes, et un drapeau rouge vif indiquant qu’un projet est en train de partir en vrille.

Le danger est que c’est si facile de copier-coller un bout de code qu’on y fait même plus attention. Et pourtant, c’est le premier pas vers la catastrophe :

  • Perte de temps : une bricole de 5m peut vous en prendre 40 pour trouver et appliquer vos modifications partout où le code se répète.
  • Risque d’erreur : il suffit d’oublier de mettre à jour une seule partie et vous êtes bon pour des heures de débuggage. C’est encore pire lorsque le développeur ne sait pas qu’il a accidentellement dupliqué du code.
  • Mauvaise lisibilité : déjà parce que le nombre de lignes alourdi le programme, mais surtout parce que deux blocs dupliqués peuvent contenir des différences subtiles, impossibles à distinguer sans yeux de lynx.
var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Smartphone, SmartphoneInfo>()
    .ForMember(dest => dest.Id, opt => opt.NullSubstitute(0))
    .ForAllOtherMembers(dest => dest.AllowNull());
});

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Smartphone, SmartphoneMeta>()
    .ForMember(dest => dest.Id, opt => opt.NullSubstitute(0))
    .ForAllOtherMembers(dest => dest.AllowNull());
});
Une petite session de codage peut vite se transformer en un jeu des 7 différences.

Bref, elle rend la maintenance frustrante et dangereuse, et comme aucun développeur n’a envie d’y toucher, personne ne réusine et tout le monde travaille à sa sauce, ce qui introduit encore plus de duplication.

Et c’est comme ça qu’en 3 mois, on arrive à des situations ou des développeurs copient-collent des classes entières. J’exagère à peine.

Ça sent le vécu !

Humf… Peut-être. Mais cela ne se serait jamais arrivé si à l’époque, nous avions maintenu la qualité du code tous le long du projet.

C’est pourquoi tous les programmeurs sans exception se doivent d’adopter les philosophies DRY (Don't Repeat Yourself) et KISS (Keep It Stupid Simple) qui incitent à combattre en permanence la duplication de code.

Avoir cette mentalité est un excellent départ, car ça montre que vous êtes conscient du problème, et donc plus disposé à faire attention. Avec ça, toutes les bonnes pratiques dont je parle dans la suite de ces articles viendront naturellement.


Combattre la duplication de code

Définition

Avant de vous gaver le cerveau de conseils, je vais exposer ce que j’appelle concrètement de la duplication.

C’est quand du code se répète !

Tu m’ôtes les mots de la bouche, espèce de pitre. Mais je vais être un peu plus spécifique que ça.

Il y a duplication lorsqu’un groupe d’instruction (ou une constante) est répété à plus d’un seul point. Plus le groupe est grand, et plus il y a de points, plus la duplication est grave. Par exemple :

// Avec duplication
int defectiveSmartphones = 0;
foreach (Smartphone smartphone in smartphones)
{
    if(smartphone.Weight > 200)
        defectiveSmartphones++;
}
return defectiveSmartphones;

int defectiveSmartphones = 0;
foreach (Smartphone smartphone in smartphones)
{
    if(smartphone.StartTime > 35000)
        defectiveSmartphones++;
}
return defectiveSmartphones;

// Sans duplication
return smartphones.Count(x => x.Weight > 200);
return smartphones.Count(x => x.StartTime > 35000);


// Avec duplication (constante magique non définie)
grid.Row[i].Cell["SmartphoneSerialNumber"].Value = smartphones[i]["SmartphoneSerialNumber"];

// Sans duplication
const string SmartphoneSerialNumberCollectionKey = "SmartphoneSerialNumber";
grid.Row[i].Cell[SmartphoneSerialNumberCollectionKey].Value = smartphones[i][SmartphoneSerialNumberCollectionKey];

Notez que je parle d’instructions et non pas de lignes, car beaucoup de lignes ne signifie pas forcément un code plus complexe.

if(defectiveSmartphonesDetected)
{
    return true;
}
else
{
    return false;
}
8 lignes absolument inutiles.
string serialNumberOfDefectiveSmartphones = defectiveSmartphones > 0 ? string.Join(", ", smartphones.Where(x => x.Weight > 200 || x.StartTime > 35000).Select(x => x.SerialNumber).ToList()) : "";
Une ligne un poil trop riche.

La base de la base

Pour corriger la duplication, peu importe les circonstances, le principe est toujours le même : rassembler le code dans un module et ne passer que par lui.

En fonction de sa portée (scope), le module peut prendre différentes formes :

  • Dans d’une fonction : variables et constantes locales.
  • Dans une classe : champs et méthodes privés.
  • Dans un composant : classes et fonctions internes.
  • Dans un programme : composants internes.
  • Dans plusieurs programmes : composants externes (packages, bibliothèques et frameworks).

En plus, ça vous permet de donner des noms à vos modules, et donc de rendre le code plus lisible.

Bien sûr, vous pouvez viser au-dessus de la portée recommandée si vous pensez que votre module vous servira ailleurs plus tard, mais dans la limite du raisonnable, n’allez pas mettre vos constantes dans un package non plus…

L’important est de garder en tête le principe d’encapsulation.


Ça se complique

Tout ce j’ai dit jusqu’ici relève du bon sens. En pratique, c’est dans les cas plus tordus que les choses deviennent hors de contrôle.

Il y a énormément de situations où il est difficile de résister à la tentation du copier-coller, mais il existe toujours une solution pour faire les choses proprement. Voici quelques cas d’école :

  • Similaire, mais pas identique : quand des groupes d’instructions se ressemblent énormément, mais pas assez être regroupés. La solution est d’utiliser l’inversion des contrôles : un principe permettant au code appelant un module d’avoir beaucoup plus d’influence sur son comportement.
On peut radicalement changer le comportement de QualityCheck() en modifiant SmartphoneQualityChecker.
  • Implémentation redondante : quand deux modules implémentent une API exactement de la même manière. La solution est d’ajouter une couche d’implémentations par défaut entre l’interface et les modules concrets.
On regroupe les procédures redondantes dans une nouvelle couche, ce qui fait que les classes concrètes implémentent beaucoup moins de fonctions.
  • Trop d’encapsulation : quand le module permettant d’éviter la duplication existe, mais est inaccessible par le code qui en a besoin. La solution est simplement d’élargir la portée de ce module.

Je ne vais pas tous les faire ici, mais comme il s’agit d’un sujet qui revient dans quasiment tous de mes articles, je finirais bien par tous les aborder 😆


Travail en aval

La triste affaire, c’est que même en faisant super gaffe, de la duplication finira toujours par passer entre les mailles du filet. C’est humain, et ce n’est pas mon pauvre article qui va corriger ça.

Mais ce n’est pas grave tant que vous apportez une double lecture à votre code, afin d’attraper les âneries in extremis avant de les pousser en production.

Il y a plein de manières de faire, la plus efficace étant la revue de code : une critique rigoureuse de son travail avant de l’intégrer au reste.

Contrairement à la croyance populaire, la revue de code est pertinente même en solo ! C’est comme un écrivain qui relit son œuvre avant de l’envoyer à son éditeur.

Une autre solution est celle des analyseurs de code : des outils qui vont dénicher les points où un réusinage est recommandé.

Bien sûr, ils ne suffisent pas à eux seuls, mais ils restent extrêmement utiles. Pour déceler la duplication, je vous invite fortement à vous intéresser à PMD, SonarQube ou encore CodeRush.

DevExpress CodeRush.

Travail en amont

Même si les bonnes pratiques et le réusinage sont importants, rien ne fait gagner plus de temps qu’un bon travail de réflexion avant de coder.

Avoir une architecture pensée pour limiter la duplication de code, c’est ça, la vraie victoire !

Facile à dire…

En effet, ce n’est pas une partie de plaisir. Ça nécessite ÉNORMÉMENT de connaissances théoriques et des années d’expérience pour faire quelque chose de vraiment impeccable.

Mais ça ne veut pas dire qu’il ne faut pas essayer. Une architecture, même imparfaite, vaut toujours mieux que rien du tout.

Le sujet est vaste, mais peut être résumé en deux principes fondamentaux :

  • La séparation des préoccupations (et le principe de responsabilité unique) : séparer et délimiter des parties du programme traitant de choses différentes.
  • La programmation modulaire : assembler ces parties en un ensemble cohérent.

Les patrons de conceptions jouent un rôle majeur dans cette architecture, car ils permettent de donner de la flexibilité au programme sans avoir à dupliquer quoi que ce soit.


Désamorcer la duplication de code

Tous ces conseils servent à combattre la duplication au quotidien afin de toujours livrer du code propre comme un sou neuf.

Mais ce n’est que la surface de l’iceberg, car bien souvent, l’excès de duplication est révélateur de problèmes beaucoup plus profonds que la simple boulette.

Il y a BEAUCOUP de facteurs qui rentrent en jeu, et comme je n’ai pas envie de trifouiller l’âme humaine, je vais juste aborder les plus fréquents.


Erreur de design

Si vous lisez mes articles sur les patrons de conception, vous remarquerez qu’une mauvaise architecture amène quasiment toujours à un de ces deux scénarios :

Ce sont les deux terminus de la dette technique, et si vous y arrivez, ça signifie que vous avez mal pensé votre codebase. C’est pour ça qu’être à l’aise avec les notions de base de l’architecture est absolument indispensable (SOLID, patrons de conception…).


Non-connaissance de la codebase

Il est très facile de dupliquer du code quand on n’est pas au courant que celui-ci existe déjà.

D’où l’intérêt de documenter rigoureusement la codebase, d’avoir une bonne convention de nommage (pour faciliter la recherche via l’autocomplétion), et surtout, de bien former les nouveaux développeurs !


Code généré automatiquement

Il n’y a rien de plus abject que les générateurs de code, et malheureusement, beaucoup en utilisent en pensant que ça va leur faire gagner du temps, alors que sur le long terme, c’est totalement l’inverse.

Je ne dis pas qu’il faut s’en débarrasser, mais au moins, relire, voir réécrire le code généré. Comme ça, vous gardez des avantages de l’outil tout en évitant le drame.


Programmation par copier-coller

C’est de très loin la source numéro 1 de duplication, logique, car c’est littéralement le principe. On utilise ce raccourci si souvent qu’il est gravé dans notre ADN, moi le premier.

Alors qu’en réalité, avec les fonctionnalités des IDE d’aujourd’hui et le réflexe des bonnes pratiques, on n’en a quasiment jamais besoin.

Au contraire, on gagne au change, car en prenant le temps de tout écrire, on apporte plus de jugement à notre code, et l’on évite les copier-vautrer qui font perdre un temps inestimable.

C’est pourquoi j’invite tous les développeurs à faire l’expérience suivante : pendant un mois, vous ne faites PAS UN SEUL copier-coller.

Je ne vous cache pas que les premiers jours vont être une torture, mais non seulement vous allez apprendre beaucoup d’autres astuces bien sympas, mais en plus, vous allez développer un meilleur esprit de jugement et faire plus attention à l’avenir !


Vous constaterez que toutes les solutions que j’ai proposées ne sont pas miraculeuses, et sont même plutôt barbantes :

  • Passer du temps en conception demande de la patience
  • Documenter une codebase est une corvée.
  • Former des développeurs coute cher
  • Se passer de ses outils et raccourcis est extrêmement frustrant.

Mais c’est justement pour ça qu’autant de codebases sont rongées par la duplication. Si c’était si facile, tout le monde le ferrait.

Pensez plutôt aux gains à long terme : au code si simple à modifier que chaque maintenance est une affaire de minutes, au client ravi de recevoir son application dans les temps et sans aucun bug, et surtout, au plaisir de travailler sur un programme impeccable.

Honnêtement, pour un résultat pareil, je suis prêt à me débarrasser du copier-coller 😉

« Si les ingénieurs de SpaceX avaient eu peur d’un peu de poussière, ils n’auraient jamais rien envoyé dans l’espace. » Léo Driat, 11 avril 2021.

Voilà pour cet article sur la duplication de code, en espérant vous avoir remplis de nouvelles connaissances !

Perso, j’ai rien appris du tout.

…Ouais, je sais bien que tout ce que j’ai dit est vraiment basique, même ma nièce aurait pu l’expliquer.

Mais comme c’est un sujet qui revient souvent sur mon blog, je devais l’aborder une bonne fois pour toutes, en espérant vous avoir piqué l’esprit un minimum

Si c’est le cas, soutenez-moi en partageant cet article (merci <3) et suivez-moi sur Twitter @ITExpertFr pour vous tenir informer des prochaines publications.

Et pour ceux qui veulent affronter le fléau à sa racine, voici une petite sélection d’articles :

Quant à moi, je vous laisse, je pars en camping-car au milieu du désert pour faire un peu de « cuisine ».

À bientôt pour un nouvel article sur le blog des développeurs ultra-efficaces. Tchao !