Einleitung: Warum Refactoring von Legacy-Code in C# eine Notwendigkeit ist
Jeder .NET-Entwickler stößt früher oder später auf Legacy-Code. Es muss nicht unbedingt ein 20 Jahre altes Projekt auf .NET Framework 1.1 sein – oft wird Code, der vor einem Jahr in Eile, ohne Tests und unter Verletzung der SOLID-Prinzipien geschrieben wurde, zum „Legacy“. Die Arbeit mit solchem Code wird zum Albtraum: Jede Änderung zerstört die Funktionalität an unerwarteten Stellen, und die Logik kann nur durch Debuggen verstanden werden.
Refactoring ist kein Neuschreiben von Grund auf. Es ist die Verbesserung der internen Code-Struktur ohne Änderung des externen Verhaltens. In diesem Artikel werden wir praktische Techniken zum Refactoring von Legacy-Code in C# besprechen, die sofort angewendet werden können, wobei die Risiken minimiert werden.
1. Vorbereitung: Die goldene Regel des Refactorings
Bevor Sie Code ändern, stellen Sie sicher, dass Sie ein „Sicherheitsnetz“ haben – eine Reihe automatischer Tests. Ohne diese wird Refactoring zum Ratespiel.
Erstellung von Characterization-Tests
Wenn keine Tests vorhanden sind, schreiben Sie Characterization-Tests (auch bekannt als Golden Master- oder Snapshot-Tests). Die Idee ist einfach: Führen Sie die Methode mit verschiedenen Eingabedaten aus, zeichnen Sie das Ergebnis auf und überprüfen Sie dann, dass sich das Ergebnis nach dem Refactoring nicht geändert hat.
// Beispiel eines Characterization-Tests für eine Legacy-Methode[Test]public void CalculateTotal_ShouldReturnSameResult_AfterRefactoring(){ // Arrange var order = new Order { Items = new List<Item> { new Item { Price = 100, Quantity = 2 } } }; var expected = 200; // Vorab bekanntes Ergebnis
// Act var result = LegacyOrderCalculator.CalculateTotal(order);
// Assert Assert.AreEqual(expected, result);}Tipp: Verwenden Sie Bibliotheken wie Verify oder ApprovalTests für automatisches Snapshot-Testing.
2. Technik Nr. 1: Extract Method
Die einfachste und effektivste Technik. Wenn Sie eine Methode mit 200 Zeilen und Kommentaren wie „// hier Rabattberechnung“ sehen, ist dies ein idealer Kandidat.
Beispiel: Große Methode
public decimal ProcessOrder(Order order){ decimal total = 0; foreach (var item in order.Items) { total += item.Price * item.Quantity; }
// Rabattberechnung if (order.Customer.IsPremium) { total *= 0.9m; // 10% Rabatt } else if (order.TotalAmount > 1000) { total *= 0.95m; // 5% Rabatt }
// Steuer anwenden total *= 1.2m; // 20% Steuer
return total;}Nach dem Refactoring
public decimal ProcessOrder(Order order){ decimal subtotal = CalculateSubtotal(order.Items); decimal afterDiscount = ApplyDiscount(subtotal, order.Customer); return ApplyTax(afterDiscount);}
private decimal CalculateSubtotal(IEnumerable<Item> items){ return items.Sum(item => item.Price * item.Quantity);}
private decimal ApplyDiscount(decimal total, Customer customer){ if (customer.IsPremium) return total * 0.9m; if (total > 1000) return total * 0.95m; return total;}
private decimal ApplyTax(decimal amount){ return amount * 1.2m;}Vorteile:
- Jede Methode hat eine einzige Verantwortung
- Der Code wird lesbar und testbar
- Die Rabattlogik kann an anderen Stellen wiederverwendet werden
3. Technik Nr. 2: Ersetzen von bedingter Logik durch Polymorphie
Legacy-Code enthält oft lange if-else- oder switch-Ketten. Dies verletzt das Open/Closed-Prinzip (OCP).
Beispiel: Switch nach Dokumenttyp
public decimal CalculateShippingCost(Order order){ switch (order.ShippingType) { case "Standard": return 5; case "Express": return 15; case "Overnight