Reprendre un legacy code base à l'aveugle : la méthode d'audit en 5 jours

par Benjamin

Un dépôt Git avec deux ans d'historique. Zéro documentation à jour. Le développeur qui a tout construit est parti il y a huit mois. Et une base de code qui, malgré tout, encaisse le trafic de production tous les jours.

C'est le point de départ le plus fréquent d'une mission d'audit technique : reprendre un legacy code base à l'aveugle, sans historique fiable ni personne à qui poser une question.

Le réflexe naturel est de vouloir tout comprendre avant d'agir, ou à l'inverse de tout réécrire pour repartir sur des bases saines. Les deux réflexes coûtent cher. Voici la méthode que j'applique pour transformer cinq jours de flou en un plan d'action exploitable.

Qu'est-ce qu'un legacy code base, exactement ?

Un legacy code base n'est pas défini par son âge, mais par l'absence de filet de sécurité pour le modifier. Michael Feathers, dans la définition devenue référence du secteur, décrit le code legacy comme du code sans tests : du code qu'on ne peut pas changer avec confiance, parce qu'on ne sait pas ce qui va casser. ^1

Un projet de six mois sans aucun test est déjà du legacy au sens strict. Un monolithe de dix ans avec une bonne couverture de tests et une documentation à jour n'en est pas vraiment, même s'il « a l'air » ancien.

Cette distinction change la façon d'auditer : l'objectif n'est pas de juger l'âge du code, mais de mesurer votre capacité réelle à le modifier sans provoquer d'incident.

L'erreur qui coûte le plus cher : réécrire avant de comprendre

Face à un code inconnu, l'envie de tout réécrire est presque toujours la première réaction. Elle est presque toujours mauvaise.

Joel Spolsky l'a documenté il y a plus de vingt ans dans un article devenu classique : une réécriture complète jette avec le mauvais code toutes les corrections de bugs, tous les cas limites et toute la connaissance métier accumulée silencieusement, ligne par ligne, pendant des années. ^2 Ce code qui semble « sale » contient souvent la trace d'un incident client résolu en production, d'une exception réglementaire, d'un cas limite qu'un partenaire a fait remonter une seule fois. Rien de tout ça n'est documenté. Tout est encodé dans le comportement du programme.

La règle que j'applique en mission : on ne réécrit jamais avant d'avoir mesuré ce qu'on risque de perdre. L'audit sert exactement à ça.

Jour 1 : cartographier avant de lire une seule ligne de logique métier

Le premier jour n'est pas consacré à comprendre le code. Il sert à comprendre sa forme, avant d'en juger le fond.

  • L'historique Git révèle l'activité réelle, pas la documentation. git log --stat et le nombre de commits par fichier identifient les zones les plus modifiées, donc les plus risquées à toucher.
  • La carte des dépendances (bibliothèques, versions, dernière mise à jour) donne une première estimation du risque de sécurité et de la dette d'upgrade.
  • La chaîne de déploiement existante (CI/CD, scripts manuels, environnements) indique si le projet peut être modifié en confiance ou si chaque changement est déjà un pari.
  • Le schéma de base de données est souvent une source de vérité plus fiable que le code lui-même : les tables, contraintes et colonnes obsolètes racontent l'histoire réelle du produit, pas celle qu'on vous raconte en réunion.
  • Le bus factor : combien de personnes dans l'équipe actuelle comprennent chaque partie critique ? Sur un legacy repris à l'aveugle, la réponse est souvent zéro, ce qui doit directement influencer la priorité de l'audit.

À la fin du jour 1, vous n'avez toujours pas de jugement sur la qualité du code. Vous avez une carte des zones à risque, ce qui est plus utile.

Jour 2 : mesurer le risque, pas la propreté du code

Un code peu élégant qui tourne sans incident depuis trois ans n'est pas une priorité. Un code fragile sur un chemin critique en est une.

Pour prioriser, je croise systématiquement deux axes :

  1. La fréquence de changement : les fichiers les plus modifiés (issus du jour 1) sont ceux où un bug coûtera le plus cher, parce qu'on va forcément y retoucher.
  2. La criticité métier : ce fichier touche-t-il au paiement, à l'authentification, à la facturation ? Ou à un écran secondaire consulté une fois par mois ?

Un fichier souvent modifié et critique pour le métier est la vraie urgence. Un fichier ancien, complexe, mais jamais touché depuis deux ans peut attendre : il ne casse rien tant que personne n'y touche.

J'ajoute à ce stade un audit des dépendances avec un outil comme OWASP Dependency-Check ou l'équivalent natif de l'écosystème (npm audit, composer audit) pour isoler les vulnérabilités connues plutôt que de les découvrir en production. ^3 Une dépendance vulnérable sur un module jamais utilisé n'est pas urgente. La même dépendance sur le module de paiement l'est.

Jour 3 : comprendre le métier avant l'architecture

C'est l'étape que la plupart des audits techniques sautent, et c'est une erreur.

Le code ne dit pas pourquoi une règle existe. Seules les personnes qui l'utilisent le savent : l'équipe support, les tickets clients archivés, les logs d'erreurs les plus fréquentes. Une fonction qui semble absurde dans le code cache presque toujours une raison métier qu'on découvre en 15 minutes de conversation, jamais en lisant le code seul.

Concrètement, je passe ce jour-là à :

  • interroger les personnes qui utilisent le produit au quotidien, pas seulement l'équipe technique restante ;
  • lire les tickets de support des trois derniers mois pour repérer les contournements que les utilisateurs ont appris à faire ;
  • identifier les flux qui génèrent réellement du revenu, par opposition aux flux qui semblent importants dans l'architecture.

Cette étape évite l'erreur classique : optimiser une partie du code techniquement intéressante, mais business-marginale, pendant que le vrai risque continue de tourner sans surveillance.

Jour 4 : construire un filet de sécurité avant de toucher au code

Une fois les zones à risque identifiées, la tentation est de commencer à corriger. Pas encore.

Michael Feathers a formalisé la technique des characterization tests : des tests qui ne vérifient pas que le code fait ce qu'il devrait faire, mais qu'il documentent ce qu'il fait réellement, aujourd'hui, en production. ^4 On capture le comportement actuel, bugs inclus, avant toute modification. Ce n'est pas un test de qualité, c'est un filet de sécurité.

Sur les zones critiques identifiées au jour 2, j'écris ces tests avant toute correction. Ils permettent de refactorer ou de corriger un bug avec la certitude de ne rien casser d'autre, même sans comprendre 100% de la logique historique.

C'est aussi le moment de repérer les candidats à une extraction progressive plutôt qu'une réécriture : la logique du Strangler Fig, popularisée par Martin Fowler, consiste à faire grandir un nouveau composant autour de l'ancien jusqu'à pouvoir le retirer, sans jamais couper le service en une seule bascule risquée. ^5

Jour 5 : livrer un plan d'action priorisé, pas un audit qui prend la poussière

Un audit qui liste 40 problèmes sans ordre de priorité n'aide personne. Il finit en PDF qu'on relit six mois plus tard en se demandant pourquoi rien n'a changé.

Le livrable que je produis distingue systématiquement trois catégories :

  • Quick wins : correction en moins d'une journée, impact direct sur la stabilité ou la sécurité (dépendance vulnérable isolée, absence de sauvegarde, secret en clair dans le repo).
  • Chantiers structurants : nécessitent un characterization test avant intervention, priorisés par le croisement fréquence de changement / criticité métier du jour 2.
  • À ne pas toucher pour l'instant : code ancien, complexe, mais stable et jamais modifié. Y toucher sans raison métier crée du risque sans bénéfice.

Cette dernière catégorie est souvent la plus contre-intuitive pour une équipe qui veut « tout nettoyer ». C'est pourtant elle qui protège le budget et le temps de l'équipe pour les vrais chantiers.

FAQ : reprendre un legacy code base

Combien de temps faut-il pour auditer un legacy code base inconnu ?

Pour une base de code d'une taille gérée par une équipe de moins de 10 développeurs, cinq jours suffisent généralement à produire une carte des risques et un plan d'action priorisé. Une compréhension complète et détaillée de chaque module prend, elle, plusieurs semaines à plusieurs mois, et n'est pas nécessaire pour commencer à intervenir en sécurité.

Faut-il réécrire un legacy code base ou le corriger progressivement ?

En première intention, non. Une réécriture complète perd la connaissance métier accumulée dans le code existant et expose à un risque de livraison différée de plusieurs mois. La bascule progressive, module par module, avec un filet de tests de caractérisation, limite le risque et permet de continuer à livrer pendant la transition.

Comment prioriser les corrections sur un code legacy sans documentation ?

Croisez la fréquence de modification d'un fichier (via l'historique Git) et sa criticité métier (paiement, authentification, données sensibles). Un fichier souvent modifié et critique est prioritaire. Un fichier ancien mais stable, jamais retouché, peut attendre.

Qu'est-ce qu'un characterization test ?

C'est un test qui documente le comportement réel et actuel d'un code, y compris ses bugs, plutôt que son comportement attendu. Il sert de filet de sécurité avant une modification, sur un code qui n'a pas de tests existants et qu'on ne comprend pas encore complètement.

Peut-on reprendre un legacy code base sans accès au développeur d'origine ?

Oui, c'est même le cas le plus fréquent en pratique. L'historique Git, le schéma de base de données, les tickets de support et les logs d'erreurs en production remplacent en grande partie la connaissance qu'aurait pu apporter le développeur d'origine, à condition de les exploiter méthodiquement plutôt que de chercher à deviner.

Ce que je recommande, en tant que CTO hands-on

Un legacy code base repris à l'aveugle n'est pas d'abord un problème de qualité de code. C'est un problème d'information manquante : personne ne sait ce qui est risqué, ce qui est stable, et pourquoi telle règle existe.

L'audit ne sert pas à juger le travail des développeurs précédents. Il sert à transformer une inconnue anxiogène en une liste d'actions ordonnées, que l'équipe peut exécuter sans peur de casser la production.

C'est la structure que j'applique dans une mission d'audit technique : cinq jours pour cartographier, prioriser et livrer un plan d'action concret. Quand le chantier dépasse l'audit ponctuel et demande une présence dans la durée, ce plan devient le point de départ d'une mission de CTO à temps partiel.

Un vieux code n'est pas dangereux parce qu'il est vieux. Il est dangereux quand personne ne sait ce qu'il fait vraiment. C'est ce deuxième problème qu'un audit doit résoudre en premier.

Sources et références

Benjamin Raimond

Benjamin Raimond

CTO hands-on : j'architecture, je code, je structure l'équipe technique. Je transforme des objectifs business en solutions robustes et durables pour les startups et PME.

En savoir plus →