Legacy
Reprendre la main sur ce que personne ne comprend plus
Ton vrai codebase n'a ni tests, ni conventions écrites, ni personne qui sache pourquoi. Ce qui change, et dans quel ordre s'y prendre.
- 01Voir pourquoi la méthode du module précédent ne tient pas telle quelle sur du legacy
- 02Se servir de l'agent pour reconstituer la connaissance que plus personne n'a
- 03Poser un filet de tests là où il n'y a rien à vérifier
- 04Extraire les conventions d'un codebase au lieu de les inventer
1. Pourquoi la méthode ne tient pas telle quelle
Le module précédent se termine sur cinq règles d'or. Sur du legacy, trois tombent d'un coup.
« Donne toujours à l'IA un moyen de vérifier son travail. » Il n'y a pas de tests. Ou il y en a, et ils sont rouges depuis 2019 parce que personne n'a jamais eu le budget pour les réparer.
« Pas de code sans plan validé par un humain. » Valider un plan suppose que tu saches ce que le code fait aujourd'hui. Sur un module que tu ouvres pour la première fois, tu n'en sais rien : tu vas valider un plan que tu n'es pas en mesure de juger.
« Les specs vivent dans le repo. » Il n'y a pas de spec. La seule spec, c'est le code, et il ment : il contient autant de décisions volontaires que d'accidents que personne n'a jamais nettoyés.
Et il y a un piège de plus, spécifique au legacy. Sur un projet moderne, quand le modèle se trompe, ça se voit : il propose un pattern qui jure avec le reste du code. Sur du legacy, c'est l'inverse. Ses propositions ont l'air meilleures que l'existant. Plus propres, plus modernes, mieux nommées. Ce n'est pas le signe qu'il a raison, c'est le signe qu'il applique le web moyen à un code qui, lui, a des raisons. Le if bizarre en haut de la fonction est peut-être un contournement pour un client qui envoie encore du XML depuis 2011. L'agent ne peut pas le deviner, et il le supprimera avec l'assurance de quelqu'un qui nettoie.
| Ce que supposait le Module 3 | Ce que tu as vraiment | Ce qu'on fait à la place |
|---|---|---|
| Des tests qui passent | Pas de tests, ou rouges depuis longtemps | On pose un filet de caractérisation (§4) |
| Des conventions écrites | Rien, ou un wiki abandonné | On les extrait du code (§5) |
| Quelqu'un qui sait pourquoi | L'auteur est parti il y a trois ans | On interroge l'historique Git (§3) |
| Un périmètre de changement clair | Tout est couplé à tout | Analyse d'impact avant la moindre ligne (§6) |
2. Sur du legacy, l'agent lit avant d'écrire
Sur du code neuf, la valeur de l'IA c'est qu'elle écrit vite. Sur du legacy, sa première valeur c'est qu'elle lit vite, et c'est là que le rapport risque/rendement est le meilleur de tout le guide.
Regarde les deux côtés. En lecture seule, le risque est nul : l'agent ne peut rien casser, tu n'as même pas besoin de relire un diff. Et le gain est énorme : reconstituer en une heure la carte d'un module que plus personne ne comprend, c'est un travail qui prend des jours à un humain seul, et que personne ne fait jamais parce que personne n'a le temps.
Alors la première session sur un codebase inconnu se passe entièrement en lecture seule. Chaque outil a sa façon de le garantir : un mode dédié qui interdit l'écriture, un profil de permissions restreint, ou à défaut une branche jetable sur laquelle tu peux tout jeter. Chez Claude Code, c'est le plan mode :
claude --permission-mode plan
Et si tu veux une garantie plus dure qu'un mode qu'on peut quitter par mégarde, ferme les outils d'écriture au niveau des permissions le temps de l'exploration. Le principe vaut partout, la syntaxe est propre à chaque outil :
// .claude/settings.local.json — le temps de la phase d'archéologie
{
"permissions": {
"deny": ["Edit(**)", "Write(**)", "Bash(git commit:*)"]
}
}
Si ton outil n'offre ni l'un ni l'autre, la version rustique fonctionne : tu explores sur une branche dédiée, et tu la supprimes en sortant. Ce qui compte n'est pas le mécanisme, c'est qu'aucune écriture de cette phase ne puisse atteindre ton code.
3. L'agent comme archéologue
Cartographier
Le premier livrable, ce n'est pas de la doc. C'est une carte : par où ça entre, par où ça sort, et où sont les mines.
Retrouver le pourquoi
Le code dit ce que ça fait. Il ne dit jamais pourquoi. Sur du legacy, la seule trace qui reste du pourquoi est dans l'historique Git, et c'est une source que l'agent sait exploiter beaucoup mieux qu'un humain pressé.
Reconstituer le vocabulaire métier
Les vieux codebases sont pleins de noms que plus personne ne sait lire : flagStatutB, traiterLot2, une table PARAM_GEN. Ce vocabulaire est la vraie clé du domaine, et il n'est écrit nulle part.
Demande à l'agent le lexique avant la doc : « Liste les termes métier récurrents dans ce module, avec pour chacun ce que le code laisse deviner de sa signification, et ton niveau de confiance. » Tu obtiens en dix minutes une liste que tu peux aller faire valider par la personne du métier qui est encore là. C'est souvent la conversation la plus rentable du projet.
4. Poser le filet : les tests de caractérisation
C'est l'idée centrale de Working Effectively with Legacy Code de Michael Feathers, et elle est plus utile que jamais maintenant qu'on a une machine pour faire le travail fastidieux.
Un test de caractérisation ne teste pas ce que le code devrait faire. Il fige ce qu'il fait, aujourd'hui, bugs compris. Ce n'est pas un test de qualité, c'est une alarme : si le comportement change, tu le sais. C'est exactement ce qui te manque pour refactorer sans trembler.
L'IA est très bonne à ce jeu, parce que c'est mécanique, volumineux et pénible : trois qualificatifs qui décrivent ce qu'on veut déléguer.
Vérifier que le filet en est un
Un filet de tests qui reste vert quoi qu'il arrive ne protège rien. Le contrôle prend deux minutes et il n'est pas négociable : va casser le code exprès.
# Change un opérateur au hasard dans la fonction couverte (> devient >=,
# && devient ||, un + devient un -), puis relance la suite.
npm test -- [fichier]
# Au moins un test doit passer au rouge. Si tout reste vert, tes tests
# n'observent rien de ce qui compte. Reprends-les avant d'aller plus loin.
git checkout [fichier] # on remet la mutation en place
C'est le principe du test de mutation, appliqué à la main. Sur un module critique, ça vaut la peine de brancher un vrai outil (Stryker en JS, mutmut en Python, Infection en PHP), mais la version manuelle suffit à disqualifier un filet inutile.
5. Extraire les conventions au lieu de les inventer
Le module suivant t'apprend à écrire ton fichier de contexte. Il suppose que tu connais tes conventions. Sur du legacy, tu ne les connais pas : elles n'ont jamais été écrites, et une partie n'a jamais été décidée.
Alors on inverse le geste : l'agent fait un premier jet à partir du code réel, et toi tu tranches.
Ce que tu récupères est une matière première, pas un fichier de contexte. Le tri qui suit, personne ne peut le faire à ta place, parce qu'il demande de savoir ce que l'équipe veut devenir. Trois catégories :
- Ce qu'on garde et qu'on impose. Les conventions qui tiennent, même si elles ne sont pas à la mode.
- Ce qu'on tolère sans l'étendre. Le code existant reste comme il est, mais on n'en écrit plus de nouveau.
- Ce qu'on interdit désormais. Les patterns qu'on a décidé de faire disparaître.
Cette deuxième catégorie est ce qui distingue un fichier de contexte de legacy d'un fichier de contexte de projet neuf. Sans elle, l'agent va « améliorer » l'ancien code à chaque passage, et tes diffs deviendront illisibles.
## Conventions de ce codebase
### On fait comme ça, on n'y touche pas
- Les repositories retournent des tableaux associatifs, pas des entités.
C'est ancien et ce n'est pas ce qu'on ferait aujourd'hui. Ne le change pas
au passage : une centaine d'appelants en dépendent.
- La casse des colonnes en base est incohérente selon les tables. C'est ainsi.
### Nouveau code uniquement
- Toute nouvelle requête passe par le QueryBuilder, jamais par du SQL concaténé.
- Tout nouveau service a ses tests. On ne rattrape pas l'existant, on ne l'aggrave pas.
IMPORTANT: ne refactorise jamais du code qui n'est pas dans le périmètre
demandé, même s'il te paraît mauvais. Signale-le, ne le touche pas.
6. Modifier : un seul type de changement par diff
La règle tient en une phrase : un diff, une nature de changement. Refactoring, ou correction de bug, ou nouvelle fonctionnalité. Jamais deux ensemble.
Ce n'est pas de la coquetterie de reviewer. Sur du legacy, c'est ce qui rend le rollback possible. Quand un diff qui mélange un refactoring et un correctif casse la prod à 18 h, tu ne sais pas quoi annuler : tu perds le correctif en annulant le refactoring. Quand les deux sont séparés, tu annules l'un et tu gardes l'autre.
L'ordre qui marche, sur du code non testé :
- Filet de caractérisation (tests verts)
- Refactoring à comportement constant (tests toujours verts)
- Le changement voulu (un test rouge, puis vert)
- Nettoyage, si on en a encore envie
L'agent tient très bien cette discipline si tu la lui donnes explicitement. Il ne la tient jamais tout seul : par défaut, il améliore ce qu'il croise.
Quand le module est trop abîmé pour être réparé
Il arrive qu'un module ne se refactore pas : trop couplé, trop long, personne ne comprend. Dans ce cas on n'y touche pas, on construit à côté et on bascule les appelants un par un. C'est le strangler fig, et c'est le seul mode où l'IA travaille confortablement sur du legacy, parce que le code neuf qu'elle écrit est un projet propre : tu peux lui donner des tests, des conventions et une spec.
La bascule reste ton travail, appelant par appelant, avec l'ancien module intact tant que le dernier n'est pas passé. Le jour où plus rien ne l'appelle, tu le supprimes, et l'agent est très bon pour te prouver que plus rien ne l'appelle.
7. Le cas de la stack morte
Angular 1.x, PHP 5.6, Struts 2, un ORM maison écrit en 2014. Ici le knowledge cutoff joue contre toi dans un sens auquel on ne pense pas : on retient qu'un modèle est faible sur ce qui est trop récent, on oublie qu'il l'est aussi sur ce qui est trop ancien. Sur un framework mort, ce qui domine ses données d'entraînement, ce sont de vieux tutoriels approximatifs et des réponses Stack Overflow contradictoires, pas une doc à jour.
Trois réflexes :
- Épingle la version dans le contexte, explicitement. « PHP 5.6, pas 8. Les fonctions et la syntaxe introduites après 5.6 n'existent pas ici. » Sans ça, le modèle glisse vers la version moderne sans prévenir, et le code ne tourne pas.
- Injecte les signatures plutôt que d'espérer. C'est l'injection just-in-time du Module 2 : pour ton ORM maison, le modèle n'a strictement rien. Colle-lui l'interface, et interdis-lui toute méthode absente de la liste.
- Méfie-toi des améliorations spontanées. Sur une stack morte, une suggestion qui « modernise » est souvent une suggestion qui ne compile pas.
8. Ce que tu ne délègues pas
Le legacy déplace la frontière posée au Module 0. Certaines zones se pilotent, elles ne se délèguent pas.
Le Lab 1 du Module 10 déroule tout ce module sur un vrai projet, en une heure : la session en lecture, l'extraction des conventions, le premier filet, et la mesure de ce que ça coûte en contexte. C'est le meilleur endroit pour commencer, et de préférence sur ton projet le moins reluisant.