Tout changer sans rien casser : ce que 6 ans de réécriture nous ont appris
Si l'on remplaçait toutes les pièces d'un bateau (chaque planche, chaque clou) est-ce que ce serait toujours le même bateau à la fin ? C'est la question que posait Plutarque à propos du bateau de Thésée, dans la mythologie grecque. Elle s'applique aussi aux organismes vivants : nous, êtres multicellulaires, sommes tous des bateaux de Thésée. Les milliards de cellules qui nous constituent naissent, se développent, se reproduisent et meurent. Une fois que toutes nos cellules ont été remplacées, sommes-nous la même personne ? Je vous laisse en débattre.
La question peut se poser à l'identique à propos du code informatique. Si l'on remplace toutes les lignes de code d'un programme, chaque variable, chaque fonction, et qu'au bout de plusieurs années il ne reste plus une ligne intacte du programme d'origine, peut-on dire qu'il s'agit du même programme ? Au fil de nos années de pratique, nous l'avons fait, et la réponse est sans appel.
NON.
Car le code est un tout autre genre d'animal.
D'abord parce qu'en dépit d'une ressemblance formelle, et parfois de fonctionnalités relativement similaires, le travail de développement informatique ne consiste jamais à préserver le statu quo : on ajoute, on enrichit, on teste, on modifie.
Mais plus profondément parce que cette refactorisation touche à l'architecture même d'un programme ; et c'est là que l'analogie avec le bateau ou l'organisme vivant s'arrête. Lorsque sur un navire on remplace une écoutille, un mât ou une voile, on préserve à l'identique la structure du bâtiment. Et lorsqu'une cellule de l'épiderme est remplacée au bout de 45 jours d'une vie intense et trépidante, sa remplaçante occupe exactement la même place et remplit strictement le même rôle (sauf si elle est maligne, ce qui n'est pas le comportement souhaité).
Dans le développement logiciel, tout est amené à changer, au plus profond de son organisation. Ne serait-ce que pour s'adapter aux évolutions de l'environnement (nouveaux usages, API tierces, IA…), mais aussi pour développer de nouvelles fonctionnalités. Un peu comme si le bateau à voile devenait à moteur, ou qu'un organisme se faisait pousser deux cœurs, remplaçait ses bras par des ailes ou son épiderme par des écailles. Ce n'est plus simplement de la réparation : on est plus proche de l'évolution des espèces, qui se fait sur plusieurs générations. Mais ici, sur un seul individu : le programme. Et c'est ça qui est vertigineux.
La magie du logiciel, c'est de s'affranchir du temps long nécessaire aux inventions techniques ou au renouvellement des générations : en quelques mois, quelques semaines, voire moins (merci l'IA), un programme peut franchir des sauts technologiques ahurissants. Tester de nouvelles fonctionnalités, échouer rapidement si c'était une mauvaise idée, mais à l'inverse itérer et accélérer si elle était plus porteuse.
C'est cette incroyable évolutivité qui explique aussi bien l'adaptation ultra-rapide des startups de la tech que les bugs et mises à jour épuisants que cette révolution permanente nous impose. Tout bouge, tout le temps, et le code qui n'avance pas est rapidement dépassé, menacé par les failles de sécurité ou l'obsolescence face aux attentes des utilisatrices et utilisateurs.
Alors il faut l'adapter, en continu. Et ce n'est pas une mince affaire.
Votre logiciel, ce château de cartes
Faire évoluer le code, c'est une part importante — parfois ingrate car invisible — du travail de développeur. Toutes les équipes connaissent ces backlogs (listes des évolutions attendues) qui grossissent au fil du temps et diminuent occasionnellement. Enrichir, ajouter, c'est la partie visible et source de satisfaction. Mais c'est aussi une zone de danger.
Car à empiler les couches de fonctionnalités sans repenser la structure, on accumule ce qu'en jargon informatique on appelle la dette technique : les choix pertinents hier ne le sont plus aujourd'hui, les bricolages et détournements s'additionnent. Et au final, le bel édifice solide du départ ressemble à un château de cartes qui n'attend qu'une secousse pour s'effondrer.
Pour reprendre l'analogie initiale, c'est comme si on ajoutait des moteurs surpuissants à une galère, mais en conservant les rames plongées dans l'eau (et les galériens qui galèrent, comme il se doit). Ou comme si on multipliait par quatre la taille des soutes sans repenser la forme de la coque ni le mode de propulsion. Bref, chaque enrichissement fonctionnel soumet l'organisation initiale à rude épreuve, et la vie d'un projet logiciel nécessite des phases de réorganisation, de réécriture — on pourrait dire de « ré-architecture » (non valable au Scrabble) — qui visent à rééquilibrer les nouvelles composantes.
Archéologie du code
Ce travail de conception en continu fait partie du quotidien. Chez Improba, nous avons eu le privilège d'hériter, à diverses reprises, de bases de code d'une qualité que nous pourrions généreusement qualifier de « médiocre ». Les clients nous ont choisis sur la promesse d'améliorer l'existant et de gagner en qualité, sur tous les plans : sécurité, performance, maintenabilité.
Et nous l'avons fait. Nous nous sommes salis les mains et fait des nœuds au cerveau. Péniblement, laborieusement, nous avons « refactoré » : naviguant dans le code spaghetti éclaté et impossible à comprendre ; humant le code smell (code odorant) précurseur d'une forme de putréfaction ; déterrant les motifs de conception inappropriés ; prenant à bras-le-corps cette complexification inévitable qu'on appelle la dette technique, qui cause des bugs et rend le travail des développeurs de plus en plus fastidieux.
La réponse, selon les situations, reposait sur des stratégies différentes et parfois complémentaires :
- L'étranglement progressif : tirant son nom du figuier étrangleur qui s'enroule autour d'un autre arbre et prend sa place jusqu'à le faire disparaître. Dans le contexte logiciel, on remplace petit à petit les pièces en préservant la structure externe (API, URLs) jusqu'à ce que le code d'origine ait disparu.
- La bulle : variante où l'on crée un îlot de code propre (la « bulle ») avec ses propres règles et son architecture cible, et où l'on migre les fonctionnalités une par une. L'ancien code continue de tourner autour, et la bulle grossit progressivement. Conceptuellement proche de l'étranglement, mais pensé de l'intérieur vers l'extérieur.
- Le parallèle : on fait tourner l'ancien et le nouveau système simultanément sur les mêmes données, on compare les résultats, et on ne bascule que quand les sorties convergent.
- La réécriture totale : plus radicale, la réécriture from scratch reste parfois inévitable, notamment sur les environnements front où l'expérience utilisateur évolue plus vite. La séparation entre le front et le back permet alors d'isoler les travaux.
Des résultats
Sur une large API (320 000 lignes de code) utilisée quotidiennement par des centaines de milliers d'utilisateurs, une trentaine de failles de sécurité majeures ont ainsi pu être refermées. Des fichiers de plus de 2 000 lignes ont été réduits et réorganisés, raccourcissant le temps passé chaque jour par les développeurs à comprendre les mécanismes sous-jacents. Le tout sans interruption de service.
Sur une autre, les performances de requêtes SQL mal optimisées ont été accélérées jusqu'à être 10 fois plus rapides. Un gain de confort pour les utilisateurs, mais aussi un allègement des ressources serveur qui évite des coûts d'infrastructure.
Sur plusieurs projets enfin, c'est le déploiement automatisé (DevOps) qui a permis de passer d'un déploiement par mois à plusieurs déploiements dans la même journée, ouvrant de nouvelles opportunités d'expérimentation rapide pour l'équipe produit.
… et quelques erreurs
Les erreurs ? Oui, nous en avons commis quelques-unes.
Erreur #1 : sous-estimer le temps nécessaire à ces réécritures tout en assurant la continuité de service est sans doute la plus manifeste : sur une application conséquente, le temps d'une refonte aboutie ne se compte pas en semaines, mais en mois, voire en années. En tout cas, c'était le cas avant l'arrivée de l'IA 😅.
Erreur #2 : lancer des travaux pharaoniques au lieu de les découper en petits morceaux. Les chantiers conséquents impliquent de créer une version parallèle du même programme, d'y travailler tout en synchronisant en continu les changements opérés sur la version principale. Notre scénario catastrophe : un développement « tunnel » de plus d'un an, des centaines de phases de synchro, et un développeur au bord de la crise de nerfs jusqu'à la réunification finale (le merge). La leçon que nous en avons tirée : procéder par itérations les plus minimes possibles, en réintégrant les évolutions au fil de l'eau.
Leçons retenues : savoir maintenir plusieurs caps
D'abord, retenons qu'un programme ne se répare pas à l'identique : il évolue, s'enrichit, se complexifie. Et l'équipe technique doit garder le cap en permanence sur plusieurs objectifs :
- d'une part, imaginer la solution cible, à laquelle on ne parviendra peut-être qu'au bout de plusieurs mois de travail ;
- d'autre part, imaginer le chemin qui y mène et qui assure la continuité de service ;
- enfin, rester agile pour incorporer en cours de route les imprévus (bugs, changements de cap) et adapter le plan aux circonstances réelles.
L'IA est un appui certain pour explorer des possibles et raccourcir les temps de développement. Mais mener cette évolution requiert une faculté de jugement et une expérience de la conduite du changement que l'IA n'est pas encore capable d'apporter. C'est pourquoi il est important d'être bien accompagné pour mener ces projets en toute sérénité.