Réusinage de code legacy en Java : niveau avancé
Travailler avec du code hérité (legacy) est une réalité inévitable pour tout développeur Java professionnel. Souvent, ce code manque de tests, contient des dépendances complexes, des classes monolithiques et des modèles obsolètes. Un simple réusinage « à vue » est dangereux – il peut entraîner des régressions et l'arrêt des processus métier. Dans cet article, nous analyserons les techniques avancées de réusinage de code legacy Java qui vous aideront à moderniser le système en toute sécurité, sans perturber son fonctionnement.
1. Stratégie des « Trois étapes » : protection avant les modifications
Le premier et le plus important principe du travail avec du code legacy est la sécurité. Ne commencez jamais un réusinage sans filet de sécurité. Utilisez la stratégie des « Trois étapes » :
- Comprendre et isoler – identifier les limites du module à modifier.
- Couverture par des tests (Characterization Tests) – figer le comportement actuel.
- Réusinage – seulement après cela, apporter les modifications.
Pour écrire des tests de caractérisation, utilisez l'approche du « test maître d'or » (Golden Master). Exécutez le code avec un ensemble de données d'entrée, sauvegardez la sortie. Ensuite, après le réusinage, comparez la nouvelle sortie avec la référence.
// Exemple : Test de caractérisation pour une méthode legacypublic class LegacyOrderProcessorTest {
@Test public void testLegacyProcessOrder() { // 1. Figer les données d'entrée Order order = new Order("LEGACY-001", List.of("item1", "item2")); LegacyOrderProcessor processor = new LegacyOrderProcessor();
// 2. Exécuter le code legacy String result = processor.processOrder(order);
// 3. Comparer avec la référence préenregistrée (Golden Master) String expected = "Processed: LEGACY-001 - items: [item1, item2]"; assertEquals(expected, result); }}Ce test ne vérifie pas la « justesse » de la logique, mais fige seulement son état actuel. Après le réusinage, le test doit réussir, garantissant la préservation du comportement.
2. Démantèlement des classes monolithiques : technique « Extraction d'interface » (Extract Interface)
Les grandes classes avec des dizaines de méthodes et de nombreuses dépendances sont un signe typique de code legacy. Au lieu d'essayer de tout réécrire d'un coup, utilisez Extract Interface.
Créez une interface décrivant le contrat public de la classe. Remplacez ensuite l'utilisation directe de la classe par l'interface. Cela permet de :
- Isoler les dépendances pour les tests.
- Extraire progressivement l'implémentation dans de nouvelles classes plus petites.
- Introduire des objets Mock sans modifier le code client.
// Classe legacy d'originepublic class HugeDataManager { public void saveData(String data) { /* ... logique complexe */ } public String loadData(String key) { /* ... */ } public void deleteAll() { /* ... opération dangereuse */ } // ... encore 50 méthodes}
// Étape 1 : Extraire l'interfacepublic interface DataRepository { void saveData(String data); String loadData(String key);}
// Étape 2 : La classe legacy implémente l'interfacepublic class HugeDataManager implements DataRepository { @Override public void saveData(String data) { /* ... */ } @Override public String loadData(String key) { /* ... */ } // deleteAll() reste uniquement dans la classe, pas dans l'interface}
// Étape 3 : Le code client travaille via l'interfacepublic class DataService { private final DataRepository repository;
public DataService(DataRepository repository) { this.repository = repository; }}Vous pouvez maintenant écrire un test pour DataService en utilisant un Mock pour DataRepository, sans toucher au complexe HugeDataManager.
3. Travail avec les collections et boucles obsolètes
Le code Java legacy (Java 7