# Retour d’expérience : refactoriser une extension vieillissante de 40 000 lignes

> Sans tests, sans namespace, avec des fonctions de 400 lignes : le récit complet d'une refonte progressive menée module par module sur un site en production.

- Auteur : Clément Hadrot
- Publié le : 2024-06-11
- Mis à jour le : 2024-06-11
- Catégorie : Extensions
- URL : https://wpmoderne.dev.wordpress-developpement.fr/extensions/refactoring-extension-vieillissante-retour-experience/

## L’essentiel

- Un refactoring big bang était exclu vu le volume de trafic
- La stratégie de la corde à strangler évite une réécriture totale
- Ajouter des tests avant de toucher au code, jamais après

L'extension métier dont il est question ici gère la logistique complète d'un site e-commerce B2B : calcul de tarifs dégressifs, gestion d'entrepôts multiples, génération de bons de livraison. Écrite entre 2014 et 2018 par plusieurs développeurs successifs, elle comptait début 2024 environ quarante mille lignes de code, aucun test automatisé, aucun namespace, et des fonctions dépassant parfois quatre cents lignes avec un niveau d'imbrication de conditions difficile à suivre visuellement.

Une réécriture complète, souvent la première idée qui vient à l'esprit face à ce constat, a été écartée d'emblée : le site traite plusieurs centaines de commandes par jour, et une réécriture big bang aurait immobilisé les évolutions métier pendant des mois, avec un risque de régression majeur au moment du basculement. Ce retour d'expérience détaille la stratégie progressive réellement mise en œuvre sur neuf mois.

## La stratégie de la corde à strangler

Le principe, emprunté au pattern « Strangler Fig » popularisé par Martin Fowler, consiste à faire coexister l'ancien code et le nouveau, en redirigeant progressivement chaque fonctionnalité vers sa version refactorisée, jusqu'à ce que l'ancien code ne soit plus jamais appelé et puisse être supprimé sans risque.

```
// Ancienne fonction, conservée mais transformée en simple passerelle
function acme_calculer_tarif_degressif( $produit_id, $quantite ) {
    // Redirection vers le nouveau service, sans changer la signature
    // pour ne pas casser les appels existants ailleurs dans le code
    return Acme\Tarification\TarifService::instance()
        ->calculer( $produit_id, $quantite );
}
```

Cette approche a permis de refactoriser un module à la fois — la tarification en premier, puis la gestion des entrepôts, puis les bons de livraison — sans jamais interrompre le fonctionnement du site pour les modules non encore traités.

## Écrire des tests avant de toucher au code, pas après

La règle la plus stricte imposée dès le premier jour : aucune ligne de l'ancien code n'était modifiée sans qu'un test de caractérisation ne soit écrit au préalable, capturant le comportement existant (même imparfait) avant toute intervention.

```
// Test de caractérisation : documente le comportement ACTUEL,
// pas le comportement souhaité, avant toute modification
public function test_tarif_degressif_comportement_actuel() {
    $tarif = acme_calculer_tarif_degressif( 42, 150 );
    // Valeur observée sur le code existant, y compris un arrondi
    // discutable qu'on choisit de documenter avant de le corriger
    $this->assertEquals( 1247.50, $tarif );
}
```

Cette discipline a révélé un bénéfice inattendu : plusieurs comportements jugés « bugués » par l'équipe se sont avérés être des règles métier volontaires, documentées nulle part mais bien réelles pour certains clients historiques avec des accords tarifaires spécifiques. Sans ces tests de caractérisation écrits avant toute modification, ces règles auraient probablement disparu silencieusement lors du refactoring.

> L'essentiel à retenir : Un refactoring big bang était exclu vu le volume de trafic ; La stratégie de la corde à strangler évite une réécriture totale ; Ajouter des tests avant de toucher au code, jamais après

## Découper les fonctions de 400 lignes : la méthode utilisée

Plutôt qu'une réécriture intégrale d'une fonction massive, chaque extraction a suivi un schéma en trois temps : identifier un bloc logique cohérent (souvent délimité par un commentaire existant, même informel), l'extraire dans une méthode dédiée avec un nom explicite, puis vérifier que les tests de caractérisation passent toujours à l'identique.

```
// Avant : un bloc noyé dans une fonction de 400 lignes
// ... 150 lignes plus haut ...
if ( $entrepot->stock_disponible( $produit_id ) < $quantite ) {
    $manquant = $quantite - $entrepot->stock_disponible( $produit_id );
    foreach ( $entrepots_secondaires as $secondaire ) {
        // 40 lignes de logique de répartition entre entrepôts
    }
}
// ... 200 lignes plus bas ...

// Après extraction, une méthode isolée et testable indépendamment
class RepartitionStockService {
    public function repartir( int $produit_id, int $quantite, Entrepot $principal ): array {
        // même logique, désormais isolée et testable seule
    }
}
```

## Ce qui a mieux fonctionné que prévu

- La coexistence ancien/nouveau code n'a généré aucune régression visible côté client pendant toute la durée du chantier
- L'équipe support a pu continuer à traiter les demandes courantes sans interruption ni formation supplémentaire
- Les tests de caractérisation, écrits au fil de l'eau, forment désormais une base de non-régression réutilisable pour toutes les évolutions futures

## Ce qui a pris plus de temps que prévu

La sous-estimation la plus significative a porté sur la découverte de dépendances cachées entre modules, en particulier des accès directs à des variables globales partagées entre la tarification et la gestion des entrepôts, invisibles tant qu'on ne cherchait pas activement à isoler chaque module. Ce travail de détection a représenté près d'un tiers du temps total du chantier, largement plus que l'écriture du nouveau code lui-même.

> La leçon la plus utile de ce chantier : le temps gagné en n'écrivant « que » le nouveau code aurait été illusoire sans la phase de détection des couplages cachés. C'est cette phase, moins visible et moins gratifiante, qui détermine en réalité la fiabilité du résultat final.

## Le résultat neuf mois plus tard

L'extension compte aujourd'hui une architecture en classes namespacées avec autoload PSR-4, une couverture de tests d'environ 65 % sur les modules refactorisés, et plus aucune fonction dépassant cent lignes. L'ancien code, initialement conservé comme passerelle, a été entièrement retiré dans les trois derniers mois du chantier, une fois la confiance dans le nouveau code suffisamment établie par l'usage réel en production.

## En résumé

Refactoriser une extension vieillissante sans interrompre son exploitation repose sur trois piliers : une stratégie progressive plutôt qu'une réécriture totale, des tests de caractérisation écrits avant toute modification, et l'acceptation qu'une part importante du temps sera consacrée à des dépendances cachées invisibles au premier abord. Le résultat, moins spectaculaire qu'une réécriture complète annoncée d'emblée, s'avère nettement plus fiable en pratique.
