14 sept. 2026

Par-delà le Big Bang : faire du refactoring une science déterministe

Développement logiciel

Imen

Ben-Abdallah

laptop computer beside coffee mug

14 sept. 2026

Par-delà le Big Bang : faire du refactoring une science déterministe

Développement logiciel

Imen

Ben-Abdallah

laptop computer beside coffee mug

14 sept. 2026

Par-delà le Big Bang : faire du refactoring une science déterministe

Développement logiciel

Imen

Ben-Abdallah

laptop computer beside coffee mug

Présentée lors de Devoxx France 2026, à Paris, cette conférence présente une approche structurée du refactoring destinée à remplacer les transformations brutales par une modernisation incrémentale. L’idée centrale est simple : plus un changement est massif, plus il accroît l’incertitude et fragilise temporairement le système. À l’inverse, des étapes limitées, réversibles et observables permettent de conserver la maîtrise de la migration.

Les dangers du « Big Bang » : pourquoi l’entropie finit par gagner

Le principal risque d’un changement massif, souvent qualifié de « Big Bang », est de provoquer un « Big Crunch » : un effondrement de la stabilité du système.

La deuxième loi de Lehman décrit une tendance à l’augmentation de la complexité des logiciels au fil de leur évolution, à moins qu’un effort continu ne soit consacré à la maîtriser. Dans ce contexte, modifier trop de composants simultanément rend l’impact du changement difficile à prévoir, à mesurer et à corriger.

Figure 1 - Chaque changement entraîne un coût temporaire de stabilité.

Le schéma illustre deux trajectoires possibles. Dans l’approche Big Bang, représentée par la courbe rouge, une modification de grande ampleur introduit de nombreuses perturbations en même temps. Les régressions se propagent, leur origine devient difficile à isoler et les autres évolutions peuvent se retrouver bloquées.

L’approche incrémentale, représentée par la courbe verte, fractionne au contraire la transformation en changements limités. Chaque baisse de stabilité reste localisée, mesurable et réversible. La modernisation devient ainsi une démarche d’ingénierie maîtrisée plutôt qu’un pari.

Sans cette approche incrémentale, plusieurs conséquences peuvent apparaître :

  • une accumulation de dette technique et de failles de sécurité, lorsque les équipes n’osent plus modifier le code,

  • des vagues de régressions difficiles à attribuer à une cause précise dans des pull requests trop volumineuses,

  • le blocage des développements parallèles et, à terme, la paralysie de plusieurs équipes pendant la migration.

Les trois axes de la modernisation

Pour conserver la maîtrise d’un refactoring, la règle d’or consiste à ne faire évoluer qu’un seul axe à la fois. Avant chaque changement, il faut donc identifier précisément ce qui bouge afin de choisir le pattern de migration adapté.

Figure 2 - Trois axes orthogonaux : topologie, brique interne et contrat.

La méthodologie distingue trois niveaux, du plus global au plus granulaire :

1. La topologie - niveau macro

  • Cet axe concerne la forme globale du système, son architecture et son infrastructure.

  • Exemple : passer d’un bundle monolithique de 4,2 Mo à plusieurs micro-frontends afin d’accroître l’autonomie des équipes.

  • Pattern associé : Strangler Fig. L’ancien système est progressivement contourné en redirigeant les appels vers de nouveaux services au moyen d’un shell ou d’un proxy, jusqu’à sa disparition complète.

2. La brique interne - niveau méso

  • Cet axe porte sur ce qui se trouve derrière l’interface visible : dépendances, configuration, outils de build ou implémentation interne.

  • Exemple : migrer 1 200 imports d’une bibliothèque d’interface obsolète vers une nouvelle solution, ou remplacer Webpack par Vite.

  • Pattern associé : Branch by Abstraction. Une couche d’abstraction est insérée entre l’application et le composant à remplacer, ce qui permet de migrer progressivement, souvent à l’aide de feature flags.

3. Le contrat - niveau micro

  • Cet axe correspond à ce que les systèmes ou utilisateurs externes voient et consomment. Il est particulièrement sensible, car toute rupture peut affecter directement les clients.

  • Exemple : renommer ou remplacer un attribut dans une API bancaire sans interrompre le service.

  • Pattern associé : Parallel Change, selon la séquence Expand, Migrate, Contract. La nouveauté est d’abord ajoutée sans supprimer l’existant ; les consommateurs sont ensuite migrés ; l’ancien contrat n’est retiré qu’après confirmation, par la télémétrie, qu’il n’est plus utilisé.

Les patterns d’un refactoring maîtrisé

1. Parallel Change - Expand, Migrate, Contract

Principalement appliqué aux contrats, ce pattern permet de faire évoluer une API sans rupture pour ses consommateurs.

  1. Expand : ajouter la nouvelle fonctionnalité ou le nouveau champ sans supprimer l’ancien. Les deux versions coexistent pendant une période de transition.

  2. Migrate : accompagner les consommateurs vers le nouveau contrat et suivre leur progression grâce à la télémétrie.

  3. Contract : supprimer l’ancien code uniquement lorsque son absence d’utilisation est confirmée.

Exemple d’application : faire évoluer le statut d’une transaction

Dans un contexte bancaire, un champ booléen peut devenir trop limité pour représenter l’ensemble des états d’une transaction.

Figure 3 - État initial : le champ is_completed ne distingue que deux états.

Dans l’état initial, l’interface Transaction utilise le champ is_completed: boolean. La valeur false signifie que la transaction est en attente, mais ce booléen ne permet pas de représenter des situations plus précises, comme un échec.

1. Expand - élargir le schéma

Figure 4 - Ajout du champ status à côté de is_completed.

Au lieu de supprimer immédiatement l’ancien champ, le schéma est étendu afin de faire cohabiter is_completed et status. Une nouvelle structure, TransactionExpanded, contient donc les deux propriétés.

Le service d’écriture adopte alors un mécanisme de dual write : il met à jour simultanément les deux champs pour maintenir leur cohérence. Par exemple, lorsqu’une transaction est finalisée, status prend la valeur completed et is_completed la valeur true.

2. Migrate - migrer les consommateurs

Une fois les deux champs déployés en production, les consommateurs sont progressivement invités à lire le nouveau champ status. La télémétrie permet de vérifier quels clients utilisent encore l’ancienne propriété.

3. Contract - retirer l’ancien champ

Lorsque la télémétrie confirme que is_completed n’est plus utilisé, ce champ peut être supprimé du code et de la base de données. La transition s’effectue ainsi sans interruption de service, tout en restant réversible pendant les premières étapes.

2. Branch by Abstraction

Adapté aux briques internes, ce pattern consiste à introduire une abstraction entre l’application et le composant à remplacer. La dépendance peut alors être migrée progressivement, composant par composant, avec l’appui éventuel de feature flags.

Exemple d’application : découpler une bibliothèque d’interface

Le cas présenté concerne un monorepo de 66 applications comportant 1 200 imports directs d’un fournisseur externe, @vendor/ui. Ce couplage rend une migration en un seul bloc trop risquée.

Figure 5 - Couplage direct des applications à la bibliothèque du fournisseur.

1. Définir un vocabulaire interne

La première étape consiste à créer une interface interne, par exemple ButtonProps, afin d’exprimer les besoins du produit avec un vocabulaire indépendant du fournisseur : primary, secondary, danger, etc.

Figure 6 - Définition d’un contrat interne indépendant du fournisseur.

2. Insérer l’abstraction

Un composant intermédiaire joue ensuite le rôle de traducteur. Il reçoit les propriétés internes et les convertit vers celles attendues par la bibliothèque externe. Dans l’exemple, la fonction Button assume cette responsabilité.

Figure 7 - Le composant intermédiaire traduit le contrat interne vers celui du fournisseur.

3. Rediriger les imports

Les applications importent désormais le composant depuis l’abstraction interne plutôt que directement depuis le fournisseur externe.

4. Basculer progressivement vers la nouvelle implémentation

La nouvelle implémentation est introduite derrière l’abstraction. Un feature flag permet de choisir dynamiquement entre l’ancienne et la nouvelle version, puis de migrer les composants par étapes.

5. Nettoyer

Lorsque la télémétrie confirme la stabilité et l’adoption de la nouvelle solution, l’ancienne implémentation, les imports devenus inutiles et, si elle n’a plus de raison d’être, la couche de transition peuvent être supprimés.

3. Strangler Fig

Utilisé pour faire évoluer la topologie d’un système, ce pattern remplace progressivement un monolithe par de nouveaux composants. Un shell ou un proxy dirige les appels vers l’ancien système ou vers les nouveaux services. À mesure que les fonctionnalités sont extraites, le monolithe se réduit jusqu’à pouvoir être supprimé.

Exemple d’application : du monolithe aux micro-frontends

L’exemple concerne un monolithe devenu lourd et complexe : un bundle de 4,2 Mo, des temps de build et de déploiement élevés, ainsi qu’un couplage qui limite l’autonomie des équipes. L’objectif est de le diviser en plusieurs micro-frontends afin de retrouver de l’agilité.

1. Préparer la nouvelle fonctionnalité en parallèle

Un premier micro-frontend, par exemple celui du paiement, est développé indépendamment du monolithe existant.

2. Mettre en place le shell

Le shell joue le rôle de proxy et assure l’aiguillage. Selon la route demandée, il dirige l’utilisateur vers l’ancien monolithe ou vers le nouveau micro-frontend.

3. Extraire puis supprimer progressivement

Les fonctionnalités sont migrées l’une après l’autre. Lorsqu’une partie est prête, elle est débranchée du monolithe. Celui-ci se réduit progressivement, à l’image de l’arbre entouré par le figuier étrangleur, jusqu’à devenir inutile. Une fois la migration achevée, le code historique et le shell transitoire peuvent être supprimés.

Méthodologie : les trois questions vitales

Pour appliquer ces patterns correctement, chaque étape doit rester déployable, réversible et observable. Avant toute modification, trois questions doivent être posées :

  1. Qu’est-ce qui bouge ? Cette question permet d’identifier l’axe concerné et de choisir le pattern approprié.

  2. Comment revenir en arrière ? Elle oblige à prévoir un mécanisme de repli et garantit la réversibilité du changement.

  3. Comment savoir que cela fonctionne ? Elle conduit à définir la télémétrie, les métriques et les critères de réussite nécessaires.

Conclusion

La modernisation logicielle ne doit être considérée ni comme un pari risqué ni comme une simple marque de prudence. Elle constitue une discipline d’ingénierie continue. En séquençant les changements selon les trois axes : topologie, brique interne et contrat et en choisissant pour chacun le pattern approprié, il devient possible de remplacer l’incertitude du Big Bang par un processus progressif, observable et maîtrisé.

Références

  • Conférence : « Refactorer sans tout casser : anatomie des patterns de modernisation incrémentale », présentée lors de Devoxx France. Vidéo : https://www.youtube.com/watch?v=68mY_vK9XfE

  • Illustrations : toutes les captures d’écran et tous les schémas techniques de ce compte rendu sont extraits du support visuel de la conférence originale.

  • Lois de l’évolution logicielle : travaux de Meir M. Lehman sur la complexité croissante des systèmes logiciels évolutifs.