La programmation est une pratique qui a révolutionné le monde. En écrivant quelques lignes de code, on peut donner vie à n'importe quelle idée.
Mais dans la réalité, les choses ne se passent pas toujours aussi bien.
Dans la plupart des projets, la productivité baisse au fur et à mesure que le temps passe. Parfois, ajouter un simple bouton dans un formulaire peut demander des semaines, voire des mois de travail.
C'est à se demander comment on a pu en arriver là ! Et surtout, comment on peut lutter contre ce phénomène.
Bien souvent, le coupable c'est la dette technique.
La bonne nouvelle, c'est qu'il est possible de la garder sous contrôle et même de s'en débarrasser.
Parlons un peu de la dette technique et des trois stratégies à mettre en place pour ne plus avoir à subir ce fléau des projets tech.
Qu'est-ce que la dette technique ?
La dette technique apparaît quand le code n'est pas adapté aux modifications qu'on doit y apporter.
Pour pouvoir ajouter ce qu'on veut sans déteriorer la qualité du projet, il faut alors fournir un travail supplémentaire en modifiant une partie du code.
Cette quantité de travail supplémentaire, c'est la dette technique. Elle résulte d'un écart entre la manière dont le code fait fonctionner le programme et l'intention qu'on veut donner au programme.
Pourquoi la dette apparaît ?
Il existe deux raisons à l'apparition de la dette technique : l'érosion logicielle et l'entropie logicielle. L'une apparaît quand on ne touche pas au code, l'autre apparaît quand on le modifie. Oui, quoi que tu fasses, tu devras faire face à la dette.
Avant de te montrer comment t'en débarasser, je vais t'expliquer pourquoi ça se produit.
L'érosion logicielle
Définition
Aussi appelé software rot [1] (décomposition logicielle) en anglais, ce principe veut que même si on ne touche pas au code, il se dégrade.
Un projet informatique n'est pas quelque chose de figé, tout bouge en permanence : les technologies se modernisent, les demandes des utilisateurs évoluent, les entreprises ne se focalisent plus sur les mêmes choses…
Or, certaines parties d'une application ne changent pas (ou très peu). Ces morceaux de code expriment toujours la vision de l'époque où ils ont été écrits, quand l'entreprise n'avait pas les mêmes buts et que les développeurs qui travaillaient dessus étaient moins expérimentés.
Progressivement, on voit apparaître un décalage entre le code et l'intention actuelle du programme. Plus le temps passe, plus ce décalage grandit.
Les initiés savent par exemple que le coeur logiciel de certaines entreprises (généralement dans le secteur de la finance) est toujours codé en Cobol (un langage créé en 1959), qu'il tourne sur des machines plus âgées que la plupart des développeurs actuels (comme les célèbres AS/400 d'IBM, qui datent des années 80) et dont le code n'a pas été modifié depuis plusieurs décennies.
Cependant, malgré la quantité astronomique d'érosion logicielle qu'elles ont accumulée, ces entreprises existent toujours. Et beaucoup d'entre elles se portent à merveille.
Le problème
On pourrait alors se demander en quoi le phénomène d'érosion est problématique.
Le problème survient quand on doit faire évoluer le logiciel. En effet, le code n'est pas adapté aux modifications qu'on veut y apporter.
Ce décalage va créer des difficultés et ralentir les équipes techniques. Pour pouvoir ajouter de nouvelles fonctionnalités, on doit parfois réécrire l'entièreté de l'application, ce qui peut prendre des années (et coûter "un pognon de dingue", comme dirait l'autre) !
D'ailleurs, les entreprises financières qui maintiennent des coeurs logiciels quadragénaires ne le font pas par plaisir.
En effet, le code a été conçu pour tourner exclusivement sur de vieilles machines, et il n'est plus possible de mettre l'environnement à jour. Souvent, le code source lui-même a été perdu (il ne reste plus qu'un fichier exécutable déjà compilé) ! Toute modification nécessiterait de remplacer l'intégralité de l'infrastructure en place ainsi que de réécrire le code en utilisant des techniques de rétro-ingénierie, ce qui est jugé trop coûteux et trop dangereux (à raison).
Bien sûr, je parle ici de cas extrêmes. En général, il suffit de quelques réunions et de quelques jours de travail pour pouvoir continuer à faire évoluer du code érodé. Mais si on répète ce processus plusieurs fois, la productivité d'une équipe peut diminuer drastiquement.
Ces problématiques sont dues à la dette technique qui s'est accumulée silencieusement pendant tout ce temps à cause de l'érosion logicielle, et qu'on doit maintenant rembourser (avec des intérêts bien sûr).
L'entropie logicielle
La deuxième cause de l'apparition de la dette technique est aussi une des principales raisons d'être du mouvement agile et du software craftsmanship : l'entropie logicielle[2].
Définition
Le concept est simple : plus on augmente la complexité d'un programme (par exemple en ajoutant de nouvelles fonctionnalités), plus on ajoute de désordre dans le code.
C'est un principe universel, même les meilleurs développeurs n'échappent pas à la règle.
Le problème
Par le passé, on pouvait ajouter rapidement des évolutions dans le code. Aujourd'hui, le code a tellement dérivé qu'on doit faire face à de la dette technique à chaque fois qu'on doit toucher à quelque chose. Modifier la moindre fonctionnalité devient un casse-tête car le code n'est pas prévu pour accueillir des changements.
En plus de ça, nos pratiques modernes n'aident pas vraiment à réduire ce phénomène.
Bien au contraire.
Quand on ajoute ou qu'on modifie une fonctionnalité dans le code, on a tendance à chercher la facilité. On fait le minimum pour que le système fonctionne et puis on passe à la suite.
Si notre code marche, pourquoi chercher à en faire plus ? Ça nous prendrait beaucoup trop de temps, il y a des tonnes d'autres choses à faire. Et si ça se trouve, il n'est pas bien vu de modifier trop de choses à la fois dans l'entreprise.
Donc on ne se prend pas la tête : on ajoute un flag par-ci, une condition par-là, et c'est plié !
En faisant ça, on crée de la complexité accidentelle[3]. Comme on ne prend pas le temps de mettre en place des solutions optimales, chaque modification ajoute un peu plus de complexité dans le code. Au fil du temps, ces briques de complexité s'accumulent et notre programme devient une grande boule de boue[4], avec des cas particuliers et des rafistolages dans tous les coins.
Cette architecture anarchique rend les prochaines modifications plus difficiles.
La vitre brisée
Le pire dans tout ça ? C'est que plus on crée de dette technique à cause de l'entropie logicielle, plus il devient compliqué de faire les choses "correctement".
Et puis à quoi bon le faire, puisque le code est déjà obsolète et chaotique ?
C'est ce qu'on appelle la théorie de la vitre brisée[5] : quand on oublie de réparer une vitre brisée dans un quartier résidentiel ( → quand on laisse traîner un morceau de code peu optimal), on se retrouve rapidement avec d'autres vitres brisées dans le coin, puis des détritus, voire de la criminalité ( → des développeurs qui ne veulent même plus produire du code de qualité). Au fur et à mesure que le temps passe, le quartier devient insalubre et plus personne ne veut y habiter.
À qui la faute ?
Bien sûr, cette complexité accidentelle est tout sauf accidentelle. A chaque fois qu'on a modifié le programme avec l'intention de faire simple, on a pris la décision implicite de ne pas faire attention à son architecture. A force de chercher la facilité, on s'est nettement compliqué la tâche sur le long terme !
On pourrait chercher les coupables pendant des jours (est-ce la faute des développeurs, des responsables de projet, de la culture d’entreprise ?), ça ne règlerait pas le problème pour autant. La dette technique resterait bien en place.
Au lieu de ça, je te propose une question plus productive :
Comment se débarrasser de la dette ?
Pour en finir avec le cercle vicieux de la dette technique, il y a 3 stratégies à employer : rembourser la dette, ne plus créer de dette et maîtriser la dette.
Chacune de ces stratégies résoud un problème différent. Bien sûr, en fonction du projet et de l'équipe dans laquelle tu te trouves, tu n'auras pas besoin de ces stratégies dans les mêmes proportions. Certaines équipes se débrouillent mieux en continuant à créer de la dette mais en mettant l'accent sur le fait de la rembourser, d'autres sont plutôt orientées vers la maîtrise de la dette. À chacun de trouver le meilleur dosage en fonction des cas.
Rembourser la dette
Cette stratégie est particulièrement adaptée aux projets comportant beaucoup de code legacy ou une architecture de code faible.
Quand on a accumulé de la dette, la première chose à faire c'est de la rembourser.
Pour ça, rien de tel que de refactoriser le code.
J'en entends déjà certains répliquer qu’on n'a pas le temps de refondre tout le projet depuis le début, et je suis totalement d'accord. Ça prendrait beaucoup de temps et mon expérience m'indique que le résultat est rarement au rendez-vous.
Donc pas de grosse révolution, pas de "version 2" du projet (c'est ce qui a tué Netscape à la fin des années 1990 alors qu'il était leader des navigateurs web). Le mieux c'est d'y aller progressivement.
On trouve une zone qui possède de la dette : un endroit complexe qu'on modifie souvent, qu'on va devoir changer dans un futur proche, ou même une vieille fonctionnalité un peu poussiéreuse, et on refactorise ça. Et puis on recommence à un autre endroit du code.
Mais attention, la dette apparaît quand le code ne correspond pas à l'intention réelle du logiciel. Si on se contente de moderniser le code sans s'attaquer à ce qu'il exprime réellement, la refacto ne sera pas efficace.
Dans l'idéal, après la refacto, ton product owner (ou même un utilisateur sans connaissances techniques) doit pouvoir lire le code et comprendre à peu près ce qu'il s'y passe. Je sais, c'est beau de rêver. Mais c'est vers ça qu'on doit tendre.
Le Comment Driven Refactoring
La bonne nouvelle, c'est qu'il existe une technique de refacto très simple : le Comment Driven Refactoring. Elle te permettra d’aligner le code avec les besoins métier, de manière ludique et sans que tu aies besoin de connaissances techniques particulières.
Résultat : le code legacy se transforme en clean code. La dette technique est alors éliminée !
Ne plus créer de dette
Cette stratégie est particulièrement efficace (mais pas que) dans les équipes expérimentées ou sur de nouveaux projets.
Se débarrasser de la dette existante, c'est bien, mais si on continue d'en générer des tonnes, on n'a pas fini de galérer !
Concrètement, au lieu de chercher à gagner 5 minutes maintenant, cette démarche te permettra de gagner 5 minutes la prochaine fois que tu devras toucher au code (et la fois d'après, et la fois d'encore après...).
En investissant dans ton code, tu transformeras la dette technique en rente technique : chaque amélioration de ton code non seulement générera des améliorations immédiates mais facilitera également les futurs changements que tu y apporteras.
Pour cela, il faut repenser la façon de modifier le code : au lieu de "coller" un morceau de logique au code existant, il doit pouvoir s’y intégrer de manière naturelle, comme si le programme avait toujours été conçu pour supporter ce nouvel élément.
C'est ici que les compétences techniques de l'équipe seront mises à l'épreuve. En effet, cette tâche demande un bon niveau en architecture logicielle. Si ça n'est pas ton cas, je te recommande de lire le livre Clean Architecture[6] de Robert C. Martin. Il a beau être considéré comme le "moins bon" livre d'Uncle Bob, la première moitié contient tout de même des conseils précieux sur la manière d'aborder l'architecture d'un logiciel, aka l'art de faire pointer des flèches dans la bonne direction.
Maîtriser la dette
Cette stratégie est très efficace dans les équipes où les développeurs ont le pouvoir de décider ce sur quoi ils vont travailler sur le court terme, ou dans les équipes où ceux qui prennent les décisions ont été sensibilisés aux problématiques de qualité du code.
Comme dans la vraie vie, la dette n'est pas toujours néfaste. Dans certains cas, on peut choisir volontairement de contracter un peu de dette à court terme pour en tirer des bénéfices.
Mais attention, pas question de contracter de la dette sans prévoir de la rembourser le plus tôt possible !
Imagine la situation suivante : un bug critique a été détecté en production. À cause de ça, ton entreprise perd l'équivalent de plusieurs milliers d'euros à chaque minute. Il faut agir vite !
On décide donc d'appliquer un correctif un peu sale (qui génère de la dette), mais qui nécessite seulement quelques minutes de travail. Ça permet de corriger rapidement le bug et de rétablir le comportement du logiciel.
Une fois l'urgence passée, on décide de rembourser la dette qu'un vient d'introduire en travaillant sur une solution plus pérenne et plus propre. Quand cette solution est prête, on remplace la rustine temporaire qu'on avait mis en place.
Et voilà, on a géré la crise en minimisant les pertes pour l'entreprise, tout en gardant un code de bonne qualité ! Pour y arriver, on a contracté de la dette technique d'une manière maîtrisée, et l'on l'a remboursée.
Conclusion
Qu'on touche au code ou pas, la dette technique apparaîtra. Tout l’enjeu est de la contenir, afin de minimiser son impact sur la viabilité du code et ainsi contribuer à la pérennité de l’entreprise qui l’utilise.
En appliquant les 3 stratégies évoquées plus haut (remboursement, prévention et maîtrise) tout au long d’un projet, la dette technique sera maintenue sous contrôle et le temps "gaspillé" sera automatiquement rentabilisé.
De plus, travailler sur une codebase saine est beaucoup plus plaisant que de travailler sur du gros legacy qui tache. Autant en profiter. 😉