Introduction : quand le code devient legacy
Chaque développeur C# travaillant dans une grande entreprise rencontre tôt ou tard des projets qui ont 5, 10, voire 15 ans. Ce code est appelé legacy. Il fonctionne, rapporte de l'argent, mais chaque modification comporte un risque. Absence de tests, couplage fort (tight coupling), classes géantes de milliers de lignes, chaînes magiques et copier-coller : voilà la réalité du legacy.
Un simple réusinage « à vue » ne fonctionnera pas ici. Des stratégies avancées sont nécessaires pour améliorer progressivement le code sans casser la logique métier. Dans cet article, nous allons examiner 4 approches clés pour le réusinage de systèmes legacy en C# (.NET) à un niveau avancé.
1. Stratégie « Strangler Fig » : remplacer des modules sans arrêter le système
Le patron Strangler Fig (figuier étrangleur) est un classique pour remplacer progressivement des composants legacy. Le principe : vous ne réécrivez pas tout le système d'un coup, mais interceptez progressivement les appels aux anciens modules et les redirigez vers les nouveaux.
Comment l'implémenter en C#
La méthode la plus simple consiste à utiliser des interfaces et des fabriques. Supposons que vous ayez une ancienne classe OldOrderProcessor utilisée dans tout le projet.
// Ancienne interface (existe déjà)public interface IOrderProcessor{ void Process(Order order);}
// Ancienne implémentation (legacy)public class OldOrderProcessor : IOrderProcessor{ public void Process(Order order) { // Code fonctionnel depuis des années, mais sale }}Créez une nouvelle implémentation et une fabrique avec un feature toggle :
public class NewOrderProcessor : IOrderProcessor{ public void Process(Order order) { // Nouveau code, propre }}
public class OrderProcessorFactory{ public IOrderProcessor Create() { // Exemple : basculement via configuration if (ConfigurationManager.AppSettings["UseNewProcessor"] == "true") return new NewOrderProcessor(); return new OldOrderProcessor(); }}Vous pouvez maintenant activer le nouveau processeur pour 5% des utilisateurs, surveiller les erreurs et augmenter progressivement le pourcentage. Lorsque l'ancien code n'est plus nécessaire, supprimez-le.
2. Extraction du noyau : Separation of Concerns via Dependency Injection
Le code legacy souffre souvent de God-classes (classes dieux) et de dépendances directes à la base de données, au système de fichiers ou aux API externes. L'introduction de Dependency Injection (DI) est la première étape vers la modularité.
Exemple pratique
Imaginez que vous ayez une classe ReportGenerator qui crée en interne un SqlConnection :
public class ReportGenerator{ public void Generate() { using (var conn = new SqlConnection("...")) { // requêtes } }}Cela ne peut pas être testé sans une vraie base de données. Réusinage :
public interface IDataRepository{ IEnumerable<DataRow> GetReportData();}
public class ReportGenerator{ private readonly IDataRepository _repository;
public ReportGenerator(IDataRepository repository) { _repository = repository; }
public void Generate() { var data = _repository.GetReportData(); // génération du rapport }}Vous pouvez maintenant remplacer le dépôt par un mock dans les tests unitaires. Utilisez le conteneur DI standard de .NET Core (Microsoft.Extensions.DependencyInjection) ou Autofac pour l'injection de dépendances.
3. Test du code legacy : Golden Master et Characterization Tests
Sans tests, le réusinage est un saut en parachute sans parachute de secours. Pour le code legacy, l'approche Characterization Tests (tests de caractérisation) ou Golden Master Testing est idéale.
Comment ça fonctionne
Vous exécutez