Archéologie logicielle : une méthode pratique pour interpréter du code hérité
Apprenez une méthode pas à pas pour enquêter en toute sécurité sur des bases de code héritées non documentées, depuis l’analyse de l’historique des modifications jusqu’au refactoring sans perturber le fonctionnement en production.
Il existe une peur particulière que chaque développeur finit par ressentir.
On ouvre un fichier qui compte plus de 4 000 lignes. Pas le moindre commentaire en vue, la moitié des variables portent des noms tels que x2 ou tempFinal_REAL, et quelque part au milieu se trouve une fonction nommée doStuff() qui, d’après ce que l’on peut en déduire, est responsable du facturation dans toute l’entreprise.
On exécute git blame et on découvre que la trace mène à quelqu’un qui a quitté l’entreprise il y a six ans. Une recherche sur Slack ne révèle rien concernant les raisons de l’existence de tout cela. La seule personne qui pourrait encore s’en souvenir est absente, et honnêtement, elle aurait probablement oublié les détails aussi.
C’est de l’archéologie logicielle.
Cela n’apparaît sur aucun organigramme ni dans aucune offre d’emploi, mais si vous passez plus d’un ou deux ans à développer du logiciel, vous l’avez déjà pratiqué. Vous examinez le code ancien de la même manière qu’un archéologue de terrain fouille un site : lentement, avec attention, en essayant de comprendre ce que les personnes qui l’ont créé avaient réellement en tête, même s’ils sont depuis longs partis et ne peuvent plus le clarifier.
Ce qui suit est un guide pour effectuer ce travail efficacement — sans perdre la raison ni perturber le fonctionnement du système en cours d’opération.
Qu’est-ce que l’archéologie logicielle, au juste ?
L’archéologie logicielle consiste à examiner, interpréter et comprendre du code ancien ou non documenté, généralement afin de pouvoir le maintenir en toute sécurité, l’étendre ou finalement le remplacer.
C’est un exercice différent de la débogage habituelle. La débogage part du principe que l’on comprend le système et qu’il y a un défaut à l’intérieur. L’archéologie logicielle part d’un principe inverse : on ne comprend pas encore du tout le système, et la première tâche réelle est d’acquérir cette compréhension avant de oser modifier quoi que ce soit.
C’est la différence entre un mécanicien qui entretient un modèle de voiture sur lequel il travaille depuis des années, et celui qui restaure une Peugeot de 1962 qu’il n’a jamais ouverte auparavant. Même jeu de clés, état d’esprit complètement différent.
La plupart des ingénieurs ne choisissent pas ce type de travail — ils y tombent par hasard. On commence un nouveau poste, et six semaines plus tard quelqu’un vous remet une tâche liée au « vieux service d’inventaire ». Soudain, on ne écrit plus de code nouveau : on fouille.
Pourquoi cette compétence est plus importante que ce que les gens admettent
Voici un fait dérangeant : la majeure partie des logiciels qui font fonctionner le monde aujourd’hui n’est pas nouvelle. Ce sont des systèmes anciens, assemblés au fil des années, partiellement compris, et qui représentent en secret une source d’anxiété pour ceux qui en ont théoriquement la responsabilité.
Les banques continuent d’utiliser du COBOL écrit dans les années 1970. Les compagnies aériennes gèrent leurs vols grâce à des systèmes plus anciens que les pilotes qui pilotent ces avions. Même les startups qui avancent à toute vitesse accumulent du code obsolète en un an ou deux — du code mis ensemble sous pression de délais par quelqu’un qui a depuis changé d’équipe, pour résoudre un problème qui n’a jamais été consigné nulle part.
Si l’unique chose que vous savez faire est écrire de nouveau code, vous êtes limité aux nouveaux projets. Mais si vous pouvez véritablement lire du code ancien — comme un détective examine une scène de crime — vous devenez la personne à qui les équipes d’ingénieurs se tournent lorsqu’il faut réparer quelque chose de complexe. C’est un avantage professionnel qui est rarement abordé ouvertement.
L’état d’esprit de l’archéologue
Au préalable, il est nécessaire de changer de perspective, ce qui facilite tout le reste.
Partez du principe que ce code a fait sens pour quelqu’un, à un moment donné.
Cette simple réinterprétation est plus importante que n’importe quelle technique de cette liste. Face à du code embrouillé et confus, il est facile de conclure que celui qui l’a écrit ne savait pas ce qu’il faisait. Ce n’est presque jamais la véritable raison. Bien plus souvent, il y avait une échéance imminente, une contrainte aujourd’hui invisible pour vous, un exigence système disparue depuis, ou une demande tout à fait raisonnable en 2016 qui n’a jamais été réexaminée depuis.
Imaginez que vous entriez dans une vieille maison et que vous aperceviez une poutre de soutien à un endroit étrange, apparemment arbitraire. Elle semble ne servir à rien — jusqu’à ce que vous appreniez qu’une muraille se trouvait autrefois à cet endroit et ait été démolie, laissant cette poutre comme seule chose à empêcher le toit de s’effondrer. Les bases de code héritées sont remplies de poutres similaires. Avant de toucher quoi que ce soit, votre tâche est de découvrir ce que chacune soutient en silence.
Adopter cette mentalité a deux effets positifs : elle vous maintient humble face à vos propres suppositions et remplace la frustration par de la curiosité. Il s’avère que la curiosité est un outil de débogage bien plus efficace que l’agacement.
Techniques pour analyser du code ancien
1. Lire l’historique des commits comme un journal
L’historique Git est ce qui se rapproche le plus d’une véritable machine à voyager dans le temps. Ne vous contentez pas d’examiner l’état actuel d’un fichier — suivez son évolution.
En exécutant git log --follow sur un fichier spécifique, on découvre souvent une véritable histoire : une fonction ajoutée en urgence juste avant une démonstration importante pour un client, une correction hâtive mise en ligne tard un vendredi soir, ou encore un commentaire indiquant « solution temporaire, à supprimer après le lancement du troisième trimestre » qui est désormais obsolète depuis quatre ans.
J’ai découvert par hasard une étrange instruction if qui traitait spécifiquement un seul ID de client, sans aucune explication associée. En vérifiant les modifications dans git blame, j’ai trouvé une note de la personne qui l’avait écrite, expliquant qu’un client en particulier avait une faute de frappe dans ses données de production, et que cette vérification servait uniquement à maintenir les choses en ordre jusqu’à ce que ce client corrige son erreur. Cette solution provisoire est restée en place silencieusement pendant cinq ans. Comprendre cette histoire a changé la manière dont l’équipe l’a finalement supprimée : ils l’ont gérée avec prudence, en effectuant une migration de données appropriée, plutôt que de simplement supprimer la vérification dans l’espoir que rien ne casserait.
2. Suivez les données, pas seulement le code
Le code vous montre ce qui pourrait arriver. Les données vous montrent ce qui a réellement eu lieu.
Allez interroger la base de données directement. Consultez les enregistrements réels. Si une table possède une colonne status avec des valeurs telles que 1, 2, 7 et 99, ne tentez pas d’en déduire le sens uniquement en lisant le code — récupérez les enregistrements réels correspondant à chacune de ces valeurs et suivez ce qui leur est arrivé dans la pratique.
Prenons par exemple un champ type dans une ancienne table de commandes où le code ne prenait en compte que les valeurs de 1 à 5, or les données de production montraient des milliers de lignes avec type = 0. Il s’est avéré que 0 signifiait « créé avant même l’existence du champ type » — un élément de l’histoire du système qui avait disparu du code actuel mais restait visible dans les données.
3. Parlez aux fantômes (ou plutôt, aux personnes encore présentes)
Même lorsque la personne qui a conçu une partie du système a disparu depuis longtemps, quelqu’un à proximité possède généralement un fragment d’information contextuelle : celui qui l’a recrutée à l’origine, l’ingénieur support qui a traité des années de tickets similaires, ou le chef de produit qui se souvient encore « de l’incident ».
Posez des questions précises et sans pression plutôt que des questions générales. « Que fait ce code ? » invite aux suppositions car il est trop ouvert. « Vous vous souvenez de quelque chose d’inhabituel concernant la gestion des remboursements vers 2021 ? » est suffisamment spécifique pour évoquer un souvenir réel.
4. Créez une carte avant de toucher à quoi que ce soit
Les archéologues ne commencent jamais à creuser au hasard — ils cartographient d’abord le site. Appliquez la même discipline à une base de code.
Dessinez, ne serait-ce que de manière approximative sur du papier ou dans un document, le parcours exact suivi par les données au sein du système : quels éléments déclenchent d’autres éléments, quelles parties stockent des informations et quels composants dépendent les uns des autres. Vous n’avez pas besoin d’un diagramme parfait — il suffit d’une représentation suffisante pour éviter que des problèmes ne surviennent.
Un astuce utile : choisissez une action concrète du monde réel, comme « un utilisateur annule son abonnement », et suivez-la du début à la fin, en notant tous les fichiers et fonctions qu’elle traverse. En suivant ce fil conducteur, on découvre souvent l’essentiel de ce qu’il faut savoir sur le système dans son ensemble.
5. Écrivez des tests avant de refactorer
Si une base de code ne contient absolument aucun test, ce qui est courant dans les systèmes anciens, résistez à l’envie de tout corriger d’un seul coup. Écrivez plutôt ce qu’on appelle des tests de caractérisation — des tests qui se contentent d’identifier ce que le code fait actuellement, que ce comportement soit correct ou non.
Cela vous offre deux avantages en même temps : un filet de sécurité pour les éventuels changements futurs, ainsi qu’une clarté forcée, car vous ne pouvez décrire le comportement actuel avec précision que si vous le comprenez réellement. La majeure partie de la valeur réside dans l’acte d’écrire ces tests, et non seulement dans leur exécution.
6. Changer une chose à la fois
Lorsque vous comprenez enfin un système complexe, la tentation est de tout réécrire d’un seul coup dans une demande de fusion triomphale. Ne le faites pas.
Les systèmes hérités ont tendance à être plus fragiles qu’ils n’y paraissent, précisément parce que personne ne possède une vision complète de toutes leurs dépendances. Apporter de petits changements réversibles — un par un, chacun étant vérifié avant de passer au suivant — est la façon d’éviter de créer un nouveau commit déroutant que des archéologues du futur devront essayer de comprendre.
7. Documentez ce que vous découvrez — pour la personne suivante
Tout ce que vous parvenez à dénicher et à comprendre mérite d’être conservé. Notez-le quelque part. Même un document court, informel et incomplet intitulé quelque chose comme "Comment fonctionne réellement le service de facturation hérité" est un cadeau pour celui qui héritera de ce code après vous — et cette personne pourriez bien être vous, dans six mois, ayant oublié tout ce dont vous vous souveniez.
Une petite histoire : La fonction que personne n’a comprise
Dans une entreprise, une fonction particulière avait reçu le surnom de « la bête » — 900 lignes enfouies profondément dans le flux de paiement que personne ne voulait toucher. Les nouveaux employés en étaient avertis lors de leur intégration, à moitié par plaisanterie, à moitié comme une véritable histoire d’avertissement, à l’instar des contes populaires locaux.
Finalement, quelqu’un a décidé de l’analyser sérieusement plutôt que de l’éviter. Au lieu d’essayer de la réécrire, ils ont suivi les transactions réelles au fur et à mesure qu’elles traversaient cette fonction, cartographié chaque branche de logique et écrit des tests de caractérisation pour couvrir chacun des chemins possibles. Tout ce processus a duré environ une semaine.
Ce qu’ils ont découvert n’était pas du désordre — il s’agissait en réalité d’un système assez cohérent pour gérer cinq fournisseurs de paiement différents, chacun avec ses propres particularités et cas limites. Il avait été conçu par quelqu’un qui résolvait des problèmes concrets sous des contraintes réelles, sans la possibilité de revenir ensuite pour tout mettre en ordre. Une fois entièrement cartographié, ce système a cessé d’être effrayant. Il restait complexe, mais c’était désormais une complexité que quelqu’un comprenait réellement.
En somme, telle est toute l’essence de cette discipline : transformer la peur en une carte.
Pensées finales
L’archéologie logicielle n’a rien de glamour. Personne ne cite « compétent pour déchiffrer du code confus d’autrui » comme une compétence majeure sur son CV. Pourtant, il s’agit d’une des capacités les plus utiles — et aussi les plus négligées — en ingénierie logicielle.
Le code ancien ne doit pas être rejeté comme étant inutile — c’est un témoignage des décisions prises sous la pression et avec des contraintes que vous ne connaissez peut-être pas entièrement. Abordez-le avec curiosité plutôt qu’avec jugement, et vous ferez des modifications plus sûres, tout en trouvant probablement ce processus bien plus gratifiant que vous ne l’auriez imaginé.
Ainsi, la prochaine fois qu’un fichier vous donnera envie de fermer le couvercle de votre ordinateur portable pour sortir, arrêtez-vous un instant et respirez. Ce que vous voyez n’est pas des débris ; c’est un site archéologique attendant d’être compris.
Prenez un pinceau, pas un bulldozer.
Lectures associées
- Neuf habitudes au niveau du code qui rendent le travail des ingénieurs experts plus fiable — Décrit neuf pratiques de codage concrètes, allant des clauses de protection à un modélisation stricte des données, qui rendent le code plus résilient, lisible et plus facile à déboguer en situation de pression.
- Dix habitudes récurrentes en JavaScript qui sabotent secrètement votre codebase — Explique dix pièges courants en JavaScript et TypeScript, allant de l’égalité lâche à la mutation d’état, et présente des patterns plus sûrs pour les remplacer.