En 2025, un client américain nous a confié une plateforme métier qui fait tourner son programme de partenaires : intégration, fiches produits, gains mensuels, paiements en plusieurs devises, et les outils quotidiens de l'équipe opérations. Elle venait d'être migrée d'une plateforme low-code vers une stack TypeScript sur mesure. Des centaines de dossiers de partenaires avaient été importés par script. Le modèle de données portait encore la forme de l'ancien outil. Les mises en production étaient rares et chacune était un petit événement.
Un an plus tard, la même plateforme livre de petits changements chaque semaine, chacun vérifié en production avant que le ticket ne soit clôturé, et l'équipe qui en dépend ouvre le ticket board plutôt qu'un fil d'emails quand quelque chose semble anormal. Rien n'a été réécrit. Voici comment nous sommes passés de l'un à l'autre, et c'est la méthode que nous appliquons à chaque sauvetage.
Semaines une et deux : tout lire, ne rien toucher
L'audit est en lecture seule. Deux semaines à lire le code, la base de données, l'infrastructure et la facture, et à parler aux gens qui utilisent le système chaque jour. Le résultat est une carte des risques : ce qui est fragile, ce qui coûte cher, ce qui va bien, et dans quel ordre s'en occuper.
Sur cette plateforme, le premier point de la carte n'était pas technique. Le chemin de l'argent, d'une commande dans le système e-commerce jusqu'au relevé mensuel d'un partenaire, traversait trois services, deux bases de données et un tableur que l'équipe opérations gardait de côté « pour vérifier ». Personne ne pouvait affirmer avec certitude que le relevé était juste. C'est devenu la priorité numéro un, avant la page d'administration lente et le déploiement capricieux, parce qu'un paiement erroné coûte la confiance, et la confiance est ce qu'un sauvetage restaure vraiment.
Le deuxième point était invisible pour le client : le pipeline de déploiement réutilisait un tag d'image quand seule la configuration changeait, si bien que certains déploiements ne faisaient silencieusement rien. Le troisième était une stack qui approchait la limite de ressources de CloudFormation, ce qui aurait bloqué tout changement d'infrastructure au pire moment possible.
Stabiliser : le rendre observable avant de l'améliorer
Les premiers changements ne sont jamais des fonctionnalités. Ce sont les choses qui permettent de modifier le système en toute sécurité : des logs structurés avec un identifiant de requête, des traces à travers les trois services, des alertes qui renvoient vers une page du runbook, et une sauvegarde qui a été restaurée au moins une fois dans un compte hors production. Sur cette plateforme, nous avons aussi remplacé un health check qui se déclarait en bonne santé alors que la base de données était injoignable, et corrigé le déploiement pour qu'un changement de configuration produise une nouvelle image (déploiement au même tag et script de déploiement sans CI).
Puis les deux ou trois correctifs qui arrêtent l'incendie hebdomadaire. Ici, c'était l'export CSV qui expirait sur les gros mois, corrigé en streamant le fichier ligne par ligne pour que le proxy ne ferme jamais une connexion inactive, et une page d'administration qui prenait vingt secondes parce qu'un calcul côté serveur tournait sur un demi-CPU. Rien de glorieux. Les deux étaient la raison pour laquelle l'équipe opérations avait cessé de faire confiance à l'outil.
La stabilisation a aussi signifié un processus. Chaque ticket va sur le ticket board du client avec un statut clair ; nous investiguons, corrigeons sur la branche de développement, déployons en production, vérifions en production avec des données réelles, et seulement alors déplaçons le ticket vers une colonne « à vérifier » pour que le client confirme. Rien n'est clôturé sur la seule foi d'un test unitaire. Cette colonne a fait plus pour la relation que n'importe quel changement technique.
Caractériser : fixer ce que fait le système avant de le changer
Le code legacy n'a pas de tests, ou des tests qui décrivent ce que quelqu'un espérait qu'il fasse. Avant de refactoriser, nous écrivons des tests de caractérisation : des tests qui capturent ce que le système fait aujourd'hui, sur des données réelles, pour que chaque changement ultérieur soit vérifié contre la réalité plutôt que contre la mémoire.
Pour le chemin de l'argent, nous avons construit exactement cela, et cela a révélé le bug que le tableur parallèle de l'équipe opérations dissimulait. Les commandes arrivent en plusieurs devises. Le calcul des gains appliquait le taux de change du jour où le relevé était généré plutôt que le taux du jour de la commande, si bien que les gains d'un partenaire changeaient selon le moment où quelqu'un cliquait sur « générer ». Un partenaire a remarqué que le chiffre ne correspondait pas à ses propres registres et a écrit.
La correction était conceptuellement simple : le taux est capturé sur la commande au moment où elle est enregistrée et n'est jamais recalculé. Le travail était dans les détails : un helper pour les colonnes JSON qui arrivent sous forme de chaînes depuis l'API de données HTTP de la base de données, un recalcul de l'historique qui a reconstitué les bons taux tout en conservant une piste d'audit des changements, une comparaison de chaque relevé avant et après, et une vérification en production par rapport aux propres chiffres du partenaire. Trois problèmes connexes signalés par la responsable financière du client ont été corrigés et vérifiés la même semaine. Les tests de caractérisation tournent désormais à chaque pull request, et le tableur parallèle a disparu.
Refactoriser le long des coutures, jamais dans une branche appelée « v2 »
Avec l'observabilité, les sauvegardes et les tests en place, refactoriser devient un travail ordinaire. Nous refactorisons le long des coutures qui comptent : les chemins qui bloquent la prochaine fonctionnalité, les chemins qui réveillent les gens la nuit, les chemins qu'un nouvel ingénieur ne peut pas lire. Jamais une branche « grand soir » qui diverge pendant trois mois.
Sur cette plateforme, les coutures étaient les données importées de l'ancien outil. Des centaines de profils de partenaires portaient des champs façonnés par le modèle de la plateforme low-code, et plusieurs centaines d'autres avaient été ajoutés plus tard via trois scripts d'import différents. Nous avons établi une source de vérité unique par champ, recalculé plus d'un millier de profils à partir d'elle, et supprimé les scripts. Un lot de codes de partenaires avait été créé avec un trait d'union en fin de chaîne et avait engendré des collections de landing dupliquées dans le système e-commerce ; nous avons supprimé quarante-neuf doublons et normalisé vingt-sept codes, en laissant délibérément intacte une poignée de codes avec des caractères non latins parce que le coût d'y toucher dépassait le bénéfice. Une course entre deux écrivains sur un index secondaire dans DynamoDB a été corrigée par une écriture conditionnelle.
Les feature flags ont fait le reste. Un type de produit qui ne devait plus être créé a reçu un interrupteur dans la configuration plutôt qu'une suppression de code, pour que l'équipe opérations puisse le rallumer si un client le demandait. Chaque refactoring a été livré derrière un flag ou sous une forme réversible en un seul déploiement.
Moderniser une fois que c'est sûr de changer
La modernisation est la dernière phase, pas la première, parce qu'elle n'est sûre qu'une fois que le système peut être observé, sauvegardé et testé. Ici, cela signifiait l'infrastructure : les stacks ont été scindées avant d'atteindre la limite CloudFormation (limite de 500 ressources CloudFormation : scinder une stack), le déploiement des conteneurs est passé dans le déploiement CDK au lieu d'une étape CLI séparée, chaque environnement a reçu ses propres domaines personnalisés, et le pipeline de déploiement a arrêté de reconstruire trois applications quand un seul fichier changeait. Les versions de framework ont été montées une version majeure à la fois, chacune derrière les tests de caractérisation.
Dans d'autres sauvetages, cette phase a été un saut de cinq versions majeures de PHP et Laravel, une migration de MySQL vers PostgreSQL faite avec une période d'écriture double et sans interruption (zéro interruption est une discipline de migration de schéma), ou un passage d'un serveur unique construit à la main vers des conteneurs et de l'infrastructure as code. La séquence est toujours la même et le système continue de tourner tout du long.
Quand la réponse est « réécrire cette partie »
Parfois, oui. Une réécriture se justifie quand le modèle de domaine est fondamentalement inadapté à ce que fait désormais l'entreprise, ou quand la plateforme sous-jacente est morte. Même alors, la réponse honnête est généralement « réécrire cette partie », pas « tout réécrire » : isoler le module, le mettre derrière une interface, le remplacer, garder le reste. L'audit indique lequel, par écrit, avec le raisonnement. Nous avons dit « réécrivons » une fois cette année et « ne réécrivons pas » de nombreuses fois, et nous ne vendons pas la réécriture parce que nous ne la facturons pas différemment.
Si vous avez un système que tout le monde a peur de déployer, ou un devis de fournisseur pour une réécriture complète sur lequel vous aimeriez un second avis, envoyez-le-nous. L'audit dure deux semaines, en lecture seule, et la carte des risques est à vous quelle que soit votre décision.