La démo fonctionne. Les utilisateurs ont commencé à arriver. Puis une modification du tarif casse les réservations, un correctif de permissions modifie trois écrans et personne ne sait pourquoi deux services calculent le même montant différemment. Vous venez d’hériter du dépôt.
Pour un développeur ou un CTO, reprendre cette base commence par une question : pouvons-nous prévoir les conséquences du prochain changement ? La réponse dépend des règles métier, des frontières du code et des moyens de vérification disponibles.
Notre article sur les avantages et limites du vibe coding aide à choisir cette approche. Le guide de reprise d’une application couvre l’audit et le passage en production. Ici, nous entrons dans le dépôt : tests, vocabulaire, modules, décisions d’architecture et migrations.
Le risque : du code que l’équipe ne peut plus expliquer
Du code généré peut être correctement conçu, relu et testé. À l’inverse, une application écrite entièrement à la main peut accumuler les mêmes difficultés. L’origine du code renseigne peu sur sa capacité à évoluer ; il faut examiner son comportement et les décisions qui le structurent.
Le rapport DORA 2025 décrit l’IA comme un amplificateur des forces et faiblesses d’une organisation. Ce constat ne mesure pas la qualité de votre dépôt. Nous en tirons une priorité pour la reprise : renforcer la compréhension et la validation avant d’accélérer à nouveau la production de code.
Un symptôme mérite particulièrement l’attention : personne ne peut expliquer une règle sans parcourir toute l’application ou demander au modèle de la reconstituer. Une explication plausible reste une hypothèse jusqu’à ce que le code exécuté, les tests et le métier la confirment.
Avant le refactoring : retrouver une version de référence
Commencez par un environnement reproductible, avec une version du code identifiée, des dépendances verrouillées et des données de test. Vérifiez que l’équipe peut déployer et restaurer le service, puis suivez un parcours critique de l’écran jusqu’au stockage et aux services externes.
Si vous découvrez une exposition de données, des accès non autorisés ou des secrets compromis, traitez ce risque avant le nettoyage architectural. Une application instable ne devient pas sûre parce que ses dossiers ont été réorganisés.
Choisissez ensuite un premier périmètre : une capacité métier qui change souvent, provoque des incidents ou bloque une livraison. Les cinq chantiers ci-dessous peuvent avancer sur ce périmètre avant d’être étendus.
1. Augmenter la testabilité avant de changer les règles
Les tests de caractérisation décrits par Michael Feathers capturent ce que fait réellement un programme. Ils permettent de détecter un changement de comportement pendant une reprise, y compris lorsque la spécification d’origine manque. Ils ne prouvent pas que ce comportement est souhaitable.
Prenons un exemple fictif de réservation, utilisé dans la suite de cet article. Avant de réorganiser le code, observez une réservation confirmée, une annulation et une demande répétée après une coupure réseau. Enregistrez les réponses et les effets produits : état de la réservation, écriture en base, notification envoyée.
En parallèle, faites valider les résultats attendus par le métier. Si le comportement actuel autorise deux confirmations pour la dernière place, conservez la reproduction du défaut et écrivez un test du résultat corrigé. Un bug connu ne doit pas devenir une exigence sous prétexte de préserver l’existant.
La testabilité s’améliore quand les dépendances peuvent être contrôlées. Séparez le calcul d’une échéance de la lecture de l’heure, et la décision de confirmer de l’appel au prestataire de paiement. Testez les règles sans réseau ; vérifiez aussi les vrais adaptateurs et contraintes de base dans des tests d’intégration.
- Règles métier : expiration, annulation, remboursement partiel, changement de tarif.
- Intégrations : concurrence sur la dernière place, réponse tardive, événement reçu deux fois.
- Parcours critiques : confirmation visible par le bon utilisateur, accès refusé depuis une autre organisation.
Pour le CTO, le premier résultat attendu est une modification utile livrée sous protection de ces tests. Le pourcentage global de couverture peut compléter ce constat ; il ne remplace pas la vérification des risques du produit.
2. Établir un langage métier partagé
Une application peut appeler la même chose « booking » dans l’interface, « order » dans l’API et « session » dans la base. Elle peut aussi employer « client » pour une personne connectée, une organisation et un compte de facturation. Cette ambiguïté finit par toucher les permissions et les règles de gestion.
Le langage partagé du Domain-Driven Design, présenté par Martin Fowler à partir des travaux d’Eric Evans, rapproche les mots des développeurs de ceux des experts métier. Il évolue avec la compréhension du domaine.
Pour notre réservation fictive, un premier atelier pourrait produire ceci :
| Terme | Signification retenue | Règle à vérifier |
|---|---|---|
| Hold | Une place temporairement retenue | Possède une échéance explicite |
| Booking | Une réservation confirmée | Respecte la capacité disponible |
| Payment | Une opération de paiement | Son état ne se confond pas avec celui de la réservation |
| Organization | Le périmètre d’accès d’une équipe | Une organisation ne lit pas les réservations d’une autre |
Ces définitions doivent être validées pour le produit concerné. Un paiement échoué annule-t-il la réservation ou ouvre-t-il un délai de régularisation ? Le code seul ne permet pas de décider ce que l’entreprise veut faire.
Reportez les termes retenus dans les types, les opérations, les tests et la documentation du domaine. Gardez une traduction explicite pour les anciennes API. Deux domaines peuvent légitimement utiliser des mots différents : les fusionner artificiellement créerait une nouvelle ambiguïté.
Un agent de code doit recevoir ces définitions et les invariants associés avant de proposer un changement. Cela donne aussi au reviewer un critère concret pour accepter ou refuser la proposition.
3. Créer des modules profonds, avec des contrats simples
Un module profond rassemble une capacité importante derrière une interface relativement simple. Dans son échange avec Robert C. Martin, John Ousterhout explique pourquoi multiplier les petites fonctions peut disperser la complexité au lieu de la réduire.
Dans notre exemple, chaque écran pourrait actuellement vérifier la disponibilité, calculer le tarif, créer une réservation et envoyer une notification. Ajouter des fichiers « helpers » laisse encore tous les appelants responsables du bon enchaînement.
Une opération métier telle que confirmBooking peut exposer un contrat plus utile : identité authentifiée, réservation concernée, clé d’idempotence et résultats possibles. Le module coordonne les règles de confirmation ; l’appelant sait aussi distinguer une place indisponible, un refus d’accès et un paiement encore en attente.
Une interface courte ne suffit pas. Le contrat doit préciser les effets et les échecs. Une transaction locale ne rend pas atomique un appel à un prestataire externe : l’état intermédiaire, la reprise après échec et la prévention des doublons doivent rester explicites.
Cherchez des responsabilités cohérentes : réservation, facturation, identité. Elles peuvent vivre dans le même déploiement. Extraire des microservices ajouterait des contraintes de réseau et d’exploitation qu’il faut justifier séparément.
Critère de validation : une évolution des règles de confirmation modifie le module et ses tests ; ses appelants restent stables tant que le contrat ne change pas. Le nombre de fichiers ou leur longueur apporte peu d’information sans cette vérification.
4. Expliquer les décisions surprenantes avec des ADR
Le code indique comment une solution fonctionne, mais laisse parfois son motif invisible. Pourquoi conserver deux identifiants ? Pourquoi traiter un événement de manière asynchrone ? Pourquoi accepter temporairement deux formats ?
Un Architecture Decision Record, ou ADR, conserve une décision significative, son contexte, son statut et ses conséquences. Michael Nygard propose des documents courts, versionnés avec le code, dont les anciennes décisions restent accessibles lorsqu’elles sont remplacées.
Voici le contenu possible d’un ADR pour notre exemple fictif :
- Contexte : le prestataire peut livrer plusieurs fois le même événement de paiement.
- Décision : enregistrer l’identifiant de l’événement et rendre son traitement idempotent.
- Option écartée : supposer que chaque livraison correspond à un nouveau paiement.
- Conséquences : définir l’unicité en base, la reprise après échec et les tests de livraisons concurrentes.
- Statut : accepté, avec un lien vers l’implémentation et les tests correspondants.
Lors d’une reprise, un agent peut aider à retrouver les indices d’une décision. S’il manque l’historique, indiquez que le motif reste à confirmer. Une justification inventée aujourd’hui ne constitue pas la mémoire du projet.
Réservez les ADR aux choix structurants. Une condition locale peut simplement demander un nom plus précis ou un commentaire expliquant la contrainte. Le livrable utile est celui qui évite qu’un futur changement supprime une protection mal comprise.
5. Automatiser les migrations répétitives
Une fois le contrat cible validé, il reste parfois des dizaines d’appels à modifier. C’est un bon candidat pour un codemod : une transformation de code que l’on peut examiner, tester et rejouer.
jscodeshift fournit des outils pour transformer plusieurs fichiers JavaScript ou TypeScript à partir de leur structure syntaxique. Cette approche permet de cibler des formes de code plutôt que de remplacer indistinctement du texte.
Dans notre exemple, une transformation pourrait remplacer les appels à l’ancien service de réservation dont les paramètres correspondent au nouveau contrat. Elle doit laisser les cas ambigus à une revue manuelle. Un arbre syntaxique ne prouve pas, à lui seul, que deux opérations ont le même sens métier.
- Tester la transformation sur des exemples avant/après, y compris les fichiers qu’elle doit ignorer.
- Examiner une simulation sur un petit périmètre et vérifier imports, alias et signatures.
- Appliquer par lots, puis exécuter typage, tests et revue du diff.
- Vérifier qu’une seconde exécution ne modifie plus le résultat et recenser les appels restants.
Une migration du code ne migre pas automatiquement les données. Pour un changement d’API ou de schéma incompatible, le pattern Parallel Change décrit par Danilo Sato distingue trois phases : étendre, migrer, retirer l’ancien contrat.
Concrètement, introduisez le nouveau format compatible avec la transition, migrez les consommateurs et les données, puis vérifiez qu’aucun ancien lecteur ou écrivain ne subsiste. La suppression vient ensuite. Si l’ancien et le nouveau système écrivent simultanément, définissez aussi comment détecter et corriger les divergences.
Le retour arrière doit couvrir les données transformées et les effets externes. Revenir au commit précédent ne suffit pas après une suppression de colonne ou l’envoi d’une notification.
Ce que le CTO doit arbitrer
La décision se prend par capacité métier. Une interface utilisable peut être conservée alors que le module de permissions doit être remplacé. La modernisation progressive décrite par Martin Fowler avec Strangler Fig fournit un cadre pour remplacer le système par étapes et vérifier les résultats au fil de la transition.
| État observé | Décision possible | Preuve attendue |
|---|---|---|
| Comportement compris, stable et vérifié | Conserver | Le prochain changement reste local et testable |
| Règles fiables, dépendances trop couplées | Refactorer progressivement | Les tests de comportement restent valides |
| Sous-système fragile avec frontière identifiable | Isoler puis remplacer | Contrat et stratégie de migration vérifiés |
| Modèle incompatible avec les besoins, reprise trop coûteuse | Évaluer une réécriture du périmètre | Comparaison incluant données, transition et exploitation |
Avant de financer une reprise complète, choisissez un pilote borné : par exemple, fiabiliser la confirmation d’une réservation et livrer une modification réelle de sa règle. Faites estimer séparément compréhension, sécurisation, refactoring et migration. Notez les inconnues et fixez un point de réévaluation.
Suivez le temps nécessaire pour livrer ce changement, les régressions, le travail de revue et la capacité de l’équipe à diagnostiquer un échec. Un dépôt avec davantage de tests et de documents, mais toujours impossible à modifier sereinement, n’a pas encore atteint l’objectif.
Continuer avec l’IA après la reprise
Les agents restent utiles pour cartographier les dépendances, proposer des scénarios limites et préparer les transformations répétitives. Demandez-leur des références précises aux fichiers, des hypothèses explicites et des changements suffisamment petits pour être relus.
Les résultats attendus d’un test doivent provenir des règles validées ou d’un comportement observé et compris. Faire générer l’implémentation et son évaluation à partir de la même supposition peut simplement reproduire l’erreur deux fois.
Le premier objectif de la reprise est concret : une autre personne de l’équipe peut expliquer, modifier, tester et déployer une capacité métier sans redécouvrir toute l’application. Les cinq chantiers servent cette autonomie.
Pour préparer une reprise avec Appik Studio, présentez-nous le parcours qui bloque et la prochaine évolution attendue. Nous pourrons cadrer l’analyse autour de ce changement, des risques associés et des parties du produit à conserver.
Sources et références
- DORA — State of AI-assisted Software Development 2025
- Michael Feathers — tests de caractérisation
- Martin Fowler — langage partagé et Domain-Driven Design
- John Ousterhout et Robert C. Martin — décomposition et complexité des modules
- Michael Nygard — documenter les décisions d’architecture
- jscodeshift — transformations de code JavaScript et TypeScript
- Danilo Sato — Parallel Change, expand / migrate / contract
- Martin Fowler — modernisation progressive avec Strangler Fig
