Cet article est publié sous licence CC BY-NC-SA
Derrière cette métaphore financière que l’on utilise régulièrement dans notre métier, se cache la mesure de difficulté à ajouter de nouvelles fonctionnalités et correctifs sur un logiciel. Elle se traduit par un surcoût sur les temps de développement qui augmentent de manière croissante et finit parfois par devenir le centre de coût principal d’un projet informatique. On la retrouve partout, et pourtant elle semble échapper à tout contrôle, fait des ravages dans les projets IT et parfois conduit à la fermeture de la boutique.
Si ça fait peur c’est accrocheur ! Digne d’un JT.
La dette technique s’accumule un peu chaque jour sur la base de différents facteurs, avant même d’écrire la première ligne de code. Les technologies qui évoluent, les recherches qui se multiplient et les savoir-faires qui s’améliorent à un rythme effréné dans un métier si jeune n’y sont pas étrangers !
Mais la dette technique n’est pas une mesure du niveau d’obsolescence des dépendances d’une application. Elle est davantage liée à la taille d’un projet et à son niveau de qualité global.
Plus un projet contient de code et de fonctionnalités et plus sa maintenance devient délicate. Et bien souvent les projets dans le temps gagnent en volume de code et fonctionnalités.
Il y a donc une érosion naturelle de la productivité inévitable qui s’applique déjà aux projets les plus qualitatifs.
Mais au-delà de cette complexité croissante il existe d’autres facteurs qui ont un impact bien plus significatifs. Tous ces facteurs sont en liens étroits avec notre capacité à faire évoluer rapidement une base de code, notre agilité. On pourrait les grouper sous l’étendard de la qualité.
La dette technique, en dehors de l’entropie inévitable qui croît avec la taille du système, est fortement influencée par des aspects qualitatifs sur lesquels il est possible d’agir. Puisqu’elle traduit la difficulté à faire évoluer un système, tout ce qui favorise l’évolution de celui-ci compense la dette et inversement.
Il est bien rare d’atteindre un résultat parfait à la première tentative. Mais il est assez fréquent de considérer comme « terminé » ce qui atteint l’objectif, sans se poser davantage de questions sur les moyens et la technique employée. À mesure que le projet avance, on accumule alors tout un tas de fonctionnalités qui peuvent satisfaire le besoin, mais ne présentent aucune finition technique. C’est ce qu’on appelle parfois dans le jargon un « Kludge », mais aussi sous d’autres aspects « Big ball of Mud » ou encore « Code Smells ».
Puis un beau jour, une nouvelle fonctionnalité nécessite que la nouvelle équipe fraichement débarquée s’appuie sur du code existant. La première estimation du coût de développement vous fera tomber de votre chaise ergonomique assis-debout. « Si on veut implémenter A, il faut qu’on refasse d’abord B et C, car le code est incompréhensible et pas réutilisable. » Je suis certain que ça vous rappellera quelque chose !
Dans l’hypothèse ou l’équipe réécrit B et C, ce sont les futures fonctionnalités reposant sur B et C qui verront leur surcoût diminué. Dans le cas contraire, tout ce qui reposera sur A, B et C aura un surcoût significativement supérieur. C’est la raison pour laquelle il ne faut pas se satisfaire du travail qui atteint l’objectif fonctionnel en dehors des PoC et proto-prototypes. Il est très important de maintenir et de renforcer la structure interne du code. De parfaire, remodeler, lisser, aléser … Car le code d’une application s’emboîte comme les pièces d’un puzzle. Les imperfections sont très sensibles voir bloquantes lors de l’ajout d’une pièce.
Tout au long du développement, les développeurs devraient consacrer du temps à repenser et réécrire leur précédent travail. Et le donneur d’ordre devrait se soucier de donner les moyens de le faire. C’est cette réécriture que l’on nomme régulièrement « refactoring ».
Boileau, depuis longtemps, nous à communiqué des règles d’or intemporelles.
Hâtez-vous lentement, et sans perdre courage, Vingt fois sur le métier remettez votre ouvrage, Polissez-le sans cesse, et le repolissez, Ajoutez quelquefois, et souvent effacez.
Il est bien question de ne pas confondre vitesse et précipitation, d’affiner progressivement le premier travail grossier, épurer et simplifier plutôt qu’entasser.
En prenant le temps de réfléchir avant d’agir, on prévient les implémentations maladroites et les raccourcis coûteux, ou Kludges.
En affinant et retravaillant on prévient les petits défauts et les irrégularités ou Code Smells.
En épurant et simplifiant, on limite l’effet de l’entropie naturelle, l’effet « Big Ball of Mud ».
Cependant, pour pouvoir retravailler sereinement les différentes parties du produit, il est nécessaire de pouvoir s’assurer qu’il remplit toujours le contrat fonctionnel tout au long du processus. Dans le cas contraire il sera difficile de conserver un outil fonctionnel sans passer par de longues phases de tests, ce qui nuirait à notre agilité ! D’où l’intérêt de développer des tests fonctionnels.
Autre facteur impactant la capacité à l’évolution. C’est la présence de tests fonctionnels automatisés. Les versions se succédant, l’éventail de fonctionnalités de l’application s’est étoffé. Mais à chaque nouvelle version, en plus du jeu de nouvelles fonctionnalités, l’équipe livre également un jeu de régressions fonctionnelles ou bugs ! Bugs et régressions dont la résolution sera du temps à investir dans la prochaine itération ou retardant la livraison de l’itération actuelle (suivant qu’il soit bloquant ou non).
Ces régressions apparaissent naturellement, car lors du développement d’une fonctionnalité se produisent des effets de bords plus ou moins importants suivant la modularité et l’isolation du code (donc l’absence de kludges & co.).Si elles sont détectées par les développeurs elles sont corrigées sur-le-champ.
Cependant, pour les détecter il faut tester l’application sur de grands pans fonctionnels en incluant les cas extrêmes et les cas standards. Plus l’application se complète et plus il est difficile d’effectuer tous ces tests, y compris dans les cas où l’équipe n’a pas changé et dispose d’un cahier de tests solide. Des erreurs finissent par échapper à la vigilance du testeur et finissent par arriver en production. Ce qui creuse la dette technique et augmente le montant des intérêts (car avant d’améliorer l’application de nouveau il faudra corriger ces défauts).
Rien de nouveau sous le soleil, même sans mener une grande campagne de refactoring, prendre le temps d’écrire des tests fonctionnels automatisés c’est capitaliser ! Ils permettent lors du développement de lancer une batterie de tests afin de s’assurer que l’on n’introduit aucune régression dans les scénarii (scénarios depuis 1990) prévus. Ils seront d’une aide redoutable dans les phases de refactoring et contribuent donc à la vélocité de développement.
C’est l’un de mes paradoxes préférés, on le retrouve souvent dans la vie quotidienne et un pilier de courants de pensées comme le Taoïsme. Pour aller vite, il faut prendre le temps… d’écrire des tests, par exemple !
C’est illusoire de se dire qu’en prenant des raccourcis on arrivera plus vite au résultat. Mais comme on l’a vu l’important n’est pas la destination, mais le voyage ! Et qu’un travail en apparence terminé n’est que la partie visible d’un iceberg de boue !
« Le sage paraît lent, mais il sait former des plans habiles. »
Dans cette approche, le travail ne se termine pas en superficie, mais en profondeur ! Il commence par la superficie, ce qui est visible et palpable par le client (pour valider la compréhension fonctionnelle du besoin) et va vers la profondeur et ce qui est le plus subtil, pour s’améliorer progressivement et gagner des propriétés supplémentaires comme la maintenabilité et la capacité d’évolution.
Ne travailler que la superficie reviendrait à faire moins de la moitié du travail. Et donc à ne livrer que l’apparence d’une solution.
Tout au long du développement d’un logiciel, la composition des équipes change, les savoir-faires aussi. Il est très fréquent de constater que la base de code d’un logiciel présente des incohérences en terme de design. Résultat de sensibilités et d’expériences différentes.
« Pour corriger cette anomalie, j’ai besoin d’une semaine, c’est Robert qui a codé ça il y a trois ans, je vais devoir relire tout le code pour comprendre ce qu’il a fait et le fonctionnel à atteindre… »
Dans un autre secteur de métier, la réponse la plus fréquente serait « Demande à Ginette, elle a travaillé un peu avec lui là-dessus… », mais dans les métiers du développement logiciel le turn-over est tellement important que le plus ancien développeur a parfois moins de deux ans d’ancienneté ! C’est une autre source d’accumulation de dette technique !
Avoir du code propre, bien entretenu et hygiénique, ainsi qu’une bonne couverture de tests fonctionnels permet de diminuer l’impact de l’absence de documentation technique du code. Cependant, avoir un guide technique et quelques documents d’architecture à jour peuvent faire gagner beaucoup de temps tout au long de la vie du projet. La documentation fonctionnelle doit elle aussi être capitalisée, pour que les objectifs et les enjeux du projets puissent être connus de tous les participants à tout instant.
Encore une fois c’est investir dans l’avenir proche du logiciel que d’écrire la documentation de ce qui existe. C’est s’assurer de donner les clés de compréhension de l’existant pour en permettre l’évolution.
Il est bien évident que pour faire évoluer un système il faut en comprendre son fonctionnement. En l’absence de documentation, la compréhension d’un système va demander de la retro-ingénerie et du tâtonnage, donc alourdir les coûts de développement avec effet boule de neige !
Donc comment peut-on dire « Je n’ai pas le temps d’écrire cette documentation » ? Le temps qui n’a pas été pris sur le moment se paiera au centuple quelque mois plus tard par quelqu’un d’autre ! Facile !
Ça fait penser à l’héritage d’une dette, ou aux patates chaudes que se transmettent nos hommes politiques de mandat en mandat. Parce que la décision prise maintenant sera la responsabilité et le fardeau d’un autre plus tard !
L’autre difficulté avec la documentation est qu’il faut qu’elle soit consultée (RTFM). Pour cela il faut qu’elle soit facilement accessible et à jour.
« Il est plus intelligent d’allumer une toute petite lampe, que de te plaindre de l’obscurité. »
Rédiger des tickets complets et avoir un tracker à jour permet également de développer avec plus d’aisance, et donc de lisser les effets de l’entropie naturelle du projet. Parcequ’ils ouvrent la porte a une compréhension fonctionnelle quasi immédiate et constituent un historique documentaire du projet.
« Ce qui s’élève repose toujours sur un fondement, sans un fond, une tasse serait inutile. »
L’effet toxique de la dette est un horrible cercle vicieux. Chaque ajout de fonctionnalité creuse la dette, mais vient aussi alourdir le coût d’ajout de prochaines fonctionnalités. Cela se traduit par un temps de développement qui augmente de façon quasi exponentielle sans une démarche qualité rigoureuse.
Les temps d’ajout de fonctionnalités et correctifs allant croissant, le coût global du développement augmente (à moins d’avoir budgetisé la dette dans les estimations), ce qui au final crée des tensions entre le financeur et l’équipe de développement. La dette technique a une réelle résonance financière !
Au sein de l’équipe de développement, celle-ci peut avoir un grand impact sur la motivation et la cohésion. La reprise de pièce de code anarchique, non testée, non documentée, est un travail pénible et long. Le développeur héritant de la dette subit une grosse pression du calendrier de livraison en plus de travailler dans la boue. Cette pression peut se traduire par des tentions entre collaborateurs ou une démotivation, ce qui accentue encore le handicap à se mouvoir et donc la fameuse dette, dans un cercle vicieux.
Chez le financeur également l’impact est assez important. Celui-ci s’est engagé sur un planning de livraison et des budgets. Au lancement du projet tout le monde est enthousiaste, car le projet avance vite puisqu’il est embryonnaire et simple. Mais au fil de l’entassement des fonctionnalités, le projet devient complexe et l’équipe met de plus en plus de temps pour en livrer de nouvelles. Les délais et le budget croissent de manière inquiétante et le responsable de projet chez le donneur d’ordre subira lui aussi une pression frustrante, puisqu’il aura l’impression que la situation échappe à son contrôle.
Mis bout à bout, c’est une belle évaporation d’énergie et d’argent.
« Attendre d’être malade pour se soigner, c’est attendre d’avoir soif pour creuser un puits. »
Il est important de savoir où l’on en est de cette dette pour pouvoir anticiper et agir avec conscience. Il peut y avoir plusieurs moyens de mesurer la dette technique. Le plus simple à mon sens est de mesurer la perte d’agilité sur le projet.
Dans un projet dont la gestion s’apparente à Scrum, il suffit de comparer la perte de vélocité de sprint en sprint. Une courbe du nombre de points de fonctionnalités achevées par sprint peut mettre en évidence la friction induite par l’entropie naturelle ainsi que les défauts de qualité. On peut aussi comparer nombre d’heures passées par point de fonctionnalité, ce qui revient à peu près au même.
Ces statistiques peuvent être mises en parallèle avec celles fournies par des outils de mesure de qualité, comme le taux de couverture de tests, les audits de qualité de code…
Il peut être aussi intéressant de comparer le rapport de régressions et bugs sur le nombre de points de fonctionnalités livrées par sprint. Un taux allant croissant peut être un symptôme de la présence de kludges & co ou d’un manque de tests fonctionnels.
« Les choses ne changent pas. Change ta façon de les voir, cela suffit. »
On peut également se servir de la dette comme d’un outil, comme le bouton « turbo » de K2000, ou encore cette supercherie de bouton turbo sur mon 486DX33Mhz.
À un moment critique il peut être nécessaire de booster la vitesse des développements en faisant des sacrifices sur le niveau de qualité. C’est un piège ! C’est un bouton magique dont on a vite tendance à vouloir abuser et à l’instar de mon 486DX33Mhz celui-ci restera en mode turbo en permanence.
La différence c’est que ce gain de vitesse immédiat au détriment de qualité se traduira en accroissement de la dette et donc en perte de vitesse à court ou moyen terme (les intérêts). Ce qui demandera également d’investir du temps dans la remise aux normes qualitatives (remboursement du capital emprunté).
Il faut donc l’utiliser comme un atout, au moment stratégique et avec parcimonie. Surtout ne pas perdre de vue que c’est un acte lourd de conséquences et qu’il faudra passer à la caisse quelques semaines plus tard.
Il arrive parfois que la friction soit tellement importante qu’on estime qu’il faut passer par une réécriture complète de la solution. Les estimations du coût d’ajout des trois prochaines fonctionnalités dépassant les estimations d’une refonte complète. C’est effacer la dette en en contractant une nouvelle. L’idée peut être judicieuse, à condition d’avoir un plan sérieux pour maîtriser la nouvelle dette.
Il faut ne pas perdre de vue qu’une refonte complète mettra en pause la livraison des fonctionnalités pendant plusieurs itérations, pour ensuite permettre d’avancer plus vite sur les prochaines. Une décision difficile à prendre sur la base d’estimations, à moins que celles-ci ne promettent une différence importante.
Il peut être également décidé de mener une campagne de restauration de l’existant, un refactoring plus large et faisant l’objet d’itérations spécifiques. Le principe et les enjeux sont les mêmes qu’une refonte complète, mais à une échelle différente.
Il faut être vigilant et se garder d’en arriver là. Cette méthode de gestion de la dette entraîne une vélocité en dents de scie, extrêmement basse lors des phases de refactoring, artificiellement haute à l’issue de celle-ci. Ce qui rend très difficile la mesure de l’avancement, la vélocité moyenne ne voulant plus rien dire. Un peu comme le débit moyen d’un fleuve qui alterne entre assèchement et crue.
Pour l’éviter il faut budgétiser et tenir compte de la dette dans les estimations et surtout investir du temps dans la qualité. Préférez-vous investir dans le remboursement d’une dette ou dans la qualité ?
Chaque itération, chaque fonctionnalité, devrait contenir une part de refactoring, de tests, de documentation… Permettant ainsi de mesurer la vélocité, qualité incluse. C’est un coût qu’il faut intégrer et accepter, ne surtout pas mener la politique de l’autruche !
Au final il y a bien quelqu’un qui finit par passer à la caisse. Ça peut être le financeur qui voit son investissement devenir inexploitable, le prestataire qui fait le dos rond et prends une partie des frais à sa charge. Voire pire, le développeur de bonne volonté qui y passera ses week-ends avant de claquer la porte. Ce qui va creuser un trou encore plus profond puisqu’il n’y a ni documentation, ni tests… Ce qui mettra malgré tout le client et le prestataire dans la situation qu’ils cherchaient à éviter.
Il faut donc tout naturellement, que prestataire et client tiennent compte de la dette existante, l’entropie naturelle, ainsi que du coût de qualité lors de leurs estimations de délais et coûts. C’est une question de transparence.
« Être conscient de la difficulté permet de l’éviter. »
« Trop loin à l’est, c’est l’ouest. »
L’application rigoureuse d’une politique de qualité vise à maintenir la vélocité de développement d’une équipe. L’application excessive d’un dogme qualitatif aura l’effet de pénaliser la vélocité, certes en contrepartie d’une qualité élevée. Mais tout est question de dosage et d’intention.
« Un effort juste, pour un effet suffisant. »
Du point de vue d’un occidental ça peut faire bondir, mais il faut bien saisir toutes les nuances du principe. Il y a des curseurs à positionner par les commanditaires d’un projet en terme de qualité. Les exigences qualitatives de développement du programme de pilotage d’un métro ne seront pas les mêmes qu’une ChatRoulette. Les conséquences d’un incident n’étant pas les mêmes, dans le premier cas des milliers de morts dans le second on évite juste un nouvel exhibitionniste ou autre tordu.
C’est l’expression de l’effet suffisant, qui permettra de définir quel est l’effort juste. Donc de définir quel niveau de qualité il convient d’atteindre, mais aussi ne pas dépasser, pour atteindre les objectifs de qualité, coûts et délais.
Ayons un nouveau regard sur ce que l’on nomme « dette technique », mesurons-la, mesurons ses conséquences, ne la nions surtout pas. Adoptons les pratiques qualitatives qui visent à libérer la vélocité de l’équipe tout en prenant bien garde de ne pas tomber dans l’excès.
L’équipe Synbioz.
Libres d’être ensemble.
Nos conseils et ressources pour vos développements produit.