Refactoring von Legacy-Code in C#: von A bis Z
Die Arbeit mit vererbtem (Legacy-)Code ist für jeden .NET-Entwickler eine unvermeidliche Realität. Alte C#-Projekte enthalten oft "Spaghetti-Code", magische Zahlen, riesige Klassen und fehlende Tests. Anstatt alles von Grund auf neu zu schreiben (was teuer und riskant ist), ermöglicht ein durchdachtes Refactoring eine schrittweise Verbesserung des Codes und reduziert die technische Schuld. In diesem Artikel analysieren wir eine schrittweise Strategie zum Refactoring von Legacy-Code in C# mit konkreten Beispielen.
1. Vorbereitung auf das Refactoring: Sicherheit geht vor
Die Hauptregel beim Refactoring von Legacy-Code lautet: Zerstören Sie nicht, was funktioniert. Ohne Tests kann jede Änderung zu Fehlern in der Produktion führen. Daher ist die erste Phase die Schaffung eines "Sicherheitsnetzes".
1.1. Hinzufügen von Tests (Characterization Tests)
Wenn keine Tests vorhanden sind, müssen sie geschrieben werden. Verwenden Sie den Ansatz der Characterization Tests (Charakterisierungstests). Die Idee: Sie führen den vorhandenen Code mit verschiedenen Eingabedaten aus und erfassen das aktuelle Verhalten als Referenz.
// Beispiel für eine Legacy-Methodepublic class LegacyCalculator{ public int CalculateTotal(int a, int b) { // Legacy-Code, den wir nicht vollständig verstehen return a + b * 2; // Verdächtige Logik }}
// Charakterisierungstest[Test]public void CalculateTotal_WhenCalled_ReturnsCurrentBehavior(){ var calc = new LegacyCalculator(); int result = calc.CalculateTotal(3, 4); // Aktuelles Ergebnis als erwartet festhalten Assert.AreEqual(11, result); // 3 + 4*2 = 11}Nach dem Schreiben solcher Tests können Sie bedenkenlos refaktorieren, da die Tests Regressionen abfangen.
1.2. Isolierung von Abhängigkeiten
Legacy-Code ist oft stark an eine Datenbank, ein Dateisystem oder externe Dienste gebunden. Verwenden Sie Extract Interface und Dependency Injection (DI) zur Isolierung.
// Vorher: starke Abhängigkeitpublic class OrderProcessor{ private readonly SqlDatabase _db = new SqlDatabase(); public void Process(Order order) { _db.Save(order); }}
// Nachher: Interface und DIpublic interface IOrderRepository{ void Save(Order order);}
public class OrderProcessor{ private readonly IOrderRepository _repository; public OrderProcessor(IOrderRepository repository) { _repository = repository; } public void Process(Order order) { _repository.Save(order); }}2. Wichtige Techniken zum Refactoring von Legacy-C#-Code
Nach der Vorbereitung kann mit der Verbesserung des Codes begonnen werden. Hier sind die wichtigsten Techniken, die bei .NET-Projekten funktionieren.
2.1. Extract Method (Methode extrahieren)
Riesige Methoden mit über 200 Zeilen sind ein Klassiker im Legacy-Code. Teilen Sie sie in kleine, lesbare Methoden auf.
// Vor dem Refactoringpublic void ProcessInvoice(Invoice invoice){ // 50 Zeilen Rabattberechnung decimal discount = 0; if (invoice.Total > 1000) discount = 0.1m; else if (invoice.Total > 500) discount = 0.05m; // ... noch viel mehr Code // 30 Zeilen E-Mail-Versand // ... }
// Nach dem Refactoringpublic void ProcessInvoice(Invoice invoice){ decimal discount = CalculateDiscount(invoice.Total); SendInvoiceEmail(invoice);}
private decimal CalculateDiscount(decimal total){ if (total > 1000) return 0.1m; if (total > 500) return 0.05m; return 0;}
private void SendInvoiceEmail(Invoice invoice){ // Versandlogik}2.2. Replace Magic Numbers with Constants
Magische Zahlen machen den Code unverständlich. Ersetzen Sie sie durch Konstanten.
// Schlechtif (user.Role == 3) { /* admin */ }
// Gutpublic enum UserRole{ Guest = 1, User = 2, Admin = 3}
if (user.Role == (int)UserRole.Admin) { /* admin */ }