Dette techniqueArchitectureLivraison logicielle

Comment réduire la dette technique : les stratégies qui fonctionnent

Réduire la dette technique, c’est intervenir aux bons endroits, dans le bon ordre, sans tout réécrire ni arrêter la livraison pendant des mois, en gardant la capacité à livrer de la valeur tout au long du processus.

QMates· Software Advisory14 avril 202611 min de lecture

La réécriture complète : un choix à haut risque déguisé en solution

Quand la dette technique devient assez visible pour atterrir sur le bureau de la direction, la proposition qui revient le plus souvent est aussi la plus risquée : tout réécrire depuis zéro. Le raisonnement séduit par son évidence : le système actuel est le problème, un nouveau système le résoudrait. Il ignore cependant des réalités opérationnelles qui rendent cette approche statistiquement risquée : elle masque des coûts importants, elle exige de maintenir l’ancien système en parallèle, et elle tend à reproduire dans le nouveau système les mêmes problèmes que l’ancien si les causes structurelles ne sont pas traitées.

La première raison, c’est que le système actuel, aussi problématique soit-il, porte des années de connaissance métier : des cas limites que plus personne ne se rappelle avoir traités, des comportements que le métier tient pour acquis, des intégrations dont la seule documentation est le code lui-même. Une réécriture repart de zéro sur cette connaissance aussi, et tend à découvrir la complexité cachée au pire moment : quand le nouveau système doit passer en production.

La deuxième, c’est le coût du parallélisme. Pendant une réécriture complète, l’équipe doit maintenir l’ancien système en production tout en développant le nouveau. La dette technique continue de brider la vitesse sur l’ancien système ; le nouveau exige des ressources à la hauteur de son ambition. En général, les deux systèmes avancent plus lentement que prévu, et le délai avant la bascule s’étire bien au-delà de l’estimation initiale.

La troisième, c’est le risque du déploiement. Un nouveau système censé remplacer entièrement l’ancien exige un moment de bascule qui constitue, par définition, le point d’exposition maximale au risque. Rien ne permet de l’éliminer totalement ; on ne peut que le repousser jusqu’au moment où il ne peut plus l’être.

Il existe des cas où une réécriture complète est le bon choix, et ils se reconnaissent à des critères précis. Les déclencheurs spécifiques (technologies qui ne sont plus maintenues, architecture si couplée qu’aucun isolement n’est possible, rupture stratégique) sont détaillés dans le prochain article de cette série, logiciel legacy : quand vivre avec, quand le faire évoluer, et quand vraiment le réécrire.

Le principe qui guide les interventions efficaces

La stratégie qui fonctionne pour réduire la dette technique reste toujours la même, quelle que soit la technologie ou le contexte : intervenir de façon incrémentale, en gardant la capacité à livrer de la valeur tout au long du processus.

Ce principe a trois implications pratiques. La première : chaque intervention doit être assez petite pour être terminée et livrée dans un délai raisonnable, en général un ou deux sprints, sans bloquer la livraison des nouvelles fonctionnalités. La deuxième : l’ordre des interventions doit être dicté par leur impact sur la livraison. La troisième : chaque intervention doit laisser le système dans un état meilleur que le précédent, complet en lui-même ; un état intermédiaire vers un objectif lointain ne suffit pas.

Ce dernier point est critique. La dette technique s’accumule justement dans ces moments où l’on travaille sur des états intermédiaires : on commence un refactoring, on s’arrête à mi-chemin pour une urgence, et l’état intermédiaire devient le nouvel état permanent. Chaque effort de réduction de dette doit être complet en lui-même : si vous ne pouvez pas le finir, ne le commencez pas.

Le Strangler Fig : la stratégie pour les systèmes legacy

Le pattern le plus efficace pour remplacer un système legacy sans réécriture complète est le Strangler Fig, proposé par Martin Fowler. Le nom vient du figuier étrangleur : une plante qui pousse autour d’un arbre hôte, l’enveloppe progressivement, et finit par le remplacer sans jamais passer par une bascule brutale.

En pratique, on construit le nouveau système autour de l’ancien. Chaque nouvelle fonctionnalité va dans le nouveau système ; les fonctionnalités existantes sont migrées une par une, de la plus simple à la plus complexe, à mesure que le nouveau système mûrit. L’ancien continue de fonctionner pour tout ce qui n’a pas encore été migré ; le nouveau grandit progressivement jusqu’à le remplacer complètement.

L’avantage de cette approche, c’est que le risque s’étale dans le temps : chaque migration est un pas petit et réversible. Le nouveau système peut être testé en production avant de remplacer entièrement l’ancien. La transition se fait graduellement, sans jour J où tout bascule : le point de non-retour est atteint quand la part de trafic sur le nouveau système devient assez élevée pour rendre l’ancien obsolète.

L’inconvénient, c’est le coût du parallélisme pendant la transition : deux systèmes à maintenir en même temps. C’est pourquoi la durée de la coexistence doit être limitée et planifiée dès le départ.

Refactoring progressif : où et comment intervenir

Pour la dette qui n’exige pas une réécriture complète, et c’est le cas de la plupart de la dette réelle, le refactoring progressif est l’approche la plus efficace. Mais le terme « refactoring » est souvent employé de façon trop large : n’importe quelle amélioration du code se fait appeler refactoring, qu’elle ait ou non un impact mesurable sur la livraison.

Le refactoring qui compte réduit le coût des changements futurs dans les zones à forte fréquence de modification, bien plus qu’il ne rend simplement le code plus joli. La distinction est pratique. Avant de lancer un refactoring, posez la question : une fois ce changement terminé, réduira-t-il le cycle time des futures modifications dans cette zone ? Si la réponse n’est pas claire, l’intervention n’a probablement pas sa place en tête des priorités.

Le point de départ du refactoring progressif reste toujours la carte de la dette construite avec les indicateurs décrits dans l’article précédent : quelles zones du système ont le cycle time le plus élevé ? Où le bug rate est-il le plus haut ? Où le coût du changement est-il disproportionné ? C’est dans ces zones que l’investissement en refactoring rapporte le plus. Refactorer les mauvaises zones est l’un des gaspillages les plus courants : cela améliore la lisibilité du code sans faire gagner la moindre vitesse de livraison.

La séquence qui fonctionne part du plus simple pour aller vers le plus complexe, en commençant par ce qui a le plus d’impact. On commence par les interventions au meilleur rapport impact/effort : souvent l’extraction de logique dupliquée, la clarification des frontières de module, la suppression de dépendances circulaires dans les zones à forte fréquence de changement, bien avant les grandes refontes architecturales. Ces interventions se bouclent en quelques jours, réduisent immédiatement la complexité opérationnelle et dégagent l’espace pour les chantiers plus vastes.

Comment maintenir la livraison pendant l’intervention

Le risque principal du refactoring, c’est qu’il ralentisse la livraison pendant le processus. Cela arrive presque toujours quand l’intervention est trop large : on touche trop de code d’un coup, on déstabilise le système, les tests échouent de façon imprévisible, et l’équipe passe plus de temps à stabiliser qu’à développer.

Les techniques qui réduisent ce risque sont bien établies. La première, c’est le branch by abstraction, introduit par Stacy Curl en 2007 et popularisé ensuite par Paul Hammant : sans modifier directement le code existant, on introduit une abstraction qui isole la partie à réécrire, on réimplémente derrière cette abstraction, on vérifie l’équivalence des comportements, et on ne retire l’ancien code qu’à ce moment-là. Le système reste ainsi fonctionnel à tout moment pendant l’intervention.

La deuxième, c’est le feature flag : le nouveau code existe en production mais reste inactif jusqu’à son activation délibérée. Cela permet de livrer le refactoring sans le rendre visible, de le tester sur une part du trafic, et de revenir en arrière en quelques secondes si un problème surgit.

La troisième, c’est une couverture de tests avant de commencer. Refactorer sans couverture suffisante revient à démonter un moteur sans savoir comment il est censé tourner. Avant de toucher une zone du système, on ajoute des tests qui décrivent son comportement actuel, même quand ce comportement n’est pas celui souhaité. Ces tests deviennent le filet de sécurité qui permet d’avancer en confiance.

La priorisation : par où commencer

Face à un système dont la dette se répartit sur plusieurs zones, la priorisation devient un problème concret : par où commencer ? La réponse se trouve dans la zone où l’investissement rapporte le plus sur la livraison à court terme, qui n’est pas forcément la plus complexe ni la plus ancienne.

Trois critères à appliquer dans l’ordre pour prioriser les interventions.

Le premier, c’est la fréquence de modification. Une zone qui change à chaque sprint et porte une forte dette est plus urgente qu’une zone qui change rarement avec le même niveau de dette. Le coût de la dette se paie à chaque modification de cette partie du système : plus elle change souvent, plus vite l’investissement en refactoring s’amortit.

Le deuxième, c’est le poids dans les dépendances. Un module dont dépendent dix autres freine bien davantage la livraison qu’un module isolé. Si le goulot d’étranglement architectural est un seul point de couplage qui ralentit des changements dans cinq zones différentes du système, intervenir là débloque cinq flux de travail à la fois.

Le troisième, c’est la réversibilité de l’intervention. Si vous doutez de l’approche, commencez par celle qui se défait le plus facilement : celle que vous pouvez annuler en quelques heures si elle ne fonctionne pas comme prévu. Les interventions irréversibles demandent une planification plus soignée et une validation en environnement contrôlé avant de passer en production.

Ce qu’il faut faire concrètement

  1. Commencez par une seule zone : la première intervention de réduction de dette doit avoir le rapport impact/effort le plus clair. Prenez la zone au cycle time le plus élevé et au bug rate le plus fort. Définissez une intervention précise, réalisable en un ou deux sprints, qui réduit le coût des changements dans cette zone. Mesurez le cycle time après coup. Si la tendance s’améliore, vous avez la confirmation que la priorisation était la bonne.
  2. Ajoutez des tests avant de toucher au code : si la zone que vous voulez refactorer manque de couverture de tests, commencez par ajouter des tests qui capturent le comportement réel du système, même quand il s’écarte de ce que le design aurait voulu. Ces tests forment le filet de sécurité qui permet de modifier le code en confiance. Sans eux, chaque changement est un risque non quantifié.
  3. Définissez un indicateur de succès avant de commencer : chaque intervention de réduction de dette doit avoir un critère de succès mesurable, aussi concret que « le cycle time des changements dans cette zone est passé de X à Y jours ». Cet indicateur permet de savoir si l’intervention a fonctionné, et de présenter à la direction le retour sur investissement dans des termes qu’elle sait lire.

Le regard de QMates

Notre approche de la réduction de dette repose sur un seul principe : ne jamais intervenir d’une façon qui bloque la capacité à livrer de la valeur. La dette technique a déjà ralenti la livraison ; une intervention qui la bloque davantage, même temporairement, aggrave le problème.

En pratique, chaque intervention que nous proposons se mène en parallèle de la livraison ordinaire. Nous intégrons le refactoring dans le flux de développement normal, sans jamais demander un « sprint de nettoyage » isolé du reste du travail, avec des critères clairs pour savoir quand une zone est assez stable pour ne plus réclamer d’attention.

Avant de réduire la dette, encore faut-il savoir laquelle : l’audit de la dette technique cartographie les zones qui freinent la livraison et les classe par ordre d’intervention. À partir de là, le refactoring du code legacy travaille ces zones de façon incrémentale, en parallèle de la livraison ordinaire, avec des mesures de départ et des critères de clôture définis avant de commencer. Si le blocage se résume à un seul point du système, une intervention ciblée suffit.

Le sixième et dernier article de cette série traite un cas précis de réduction de dette, celui du système legacy : quand vivre avec, quand le faire évoluer, et quand vraiment le réécrire.

Dites-nous où vous êtes bloqué

Prototype fragile, legacy lourd ou livraison imprévisible, c’est notre point de départ

Nous utilisons vos données pour répondre à votre demande. Consultez notre Politique de confidentialité.