Refactoring von Legacy-Code in C#: Wie man ein veraltetes Projekt in ein modernes .NET-Produkt verwandelt
Jeder .NET-Entwickler stößt früher oder später auf Legacy-Code – monolithische Klassen mit 3000 Zeilen, Spaghetti-Logik in .aspx.cs-Dateien und magische Zeichenfolgen, die über das gesamte Projekt verstreut sind. Das Refactoring solchen Codes ist nicht nur ein "Kämmen" der Syntax, sondern eine strategische Aufgabe: die Architektur verbessern, ohne die Geschäftslogik zu beschädigen. In diesem Artikel werden wir praktische Ansätze zum Refactoring von C#-Code besprechen, die Ihnen helfen, geerbte Projekte sicher und effizient zu modernisieren.
1. Vorbereitung: Warum das Refactoring von Legacy-Code einen besonderen Ansatz erfordert
Der häufigste Fehler von Anfängern ist es, sofort mit dem Umschreiben des Codes zu beginnen. In C# .NET Legacy-Projekten gibt es oft versteckte Abhängigkeiten: statische Klassen, globale Zustände, enge Verbindungen zur Datenbank über ADO.NET. Jede Änderung kann eine Kaskade von Fehlern auslösen.
Hauptmerkmale von Legacy-Code in C#:
- Fehlende Modultests – der Code ist nicht mit Unit-Tests abgedeckt, was das Refactoring riskant macht.
- God Objects – Klassen, die alles machen: von der Datenbankarbeit bis zum UI-Rendering.
- Harte Abhängigkeiten – direkte Instanziierung über
new SqlConnection()innerhalb von Methoden. - Magische Zeichenfolgen und Zahlen – Konfigurationen, die in den Code eingebettet sind, anstatt in
app.configoderappsettings.json. - Verwendung veralteter APIs – z.B.
WebClientanstelle vonHttpClientoderArrayListanstelle vonList<T>.
Vor Beginn des Refactorings unbedingt: Anforderungen vom Kunden einfrieren, ein Versionskontrollsystem (Git) einrichten und ein "Sicherheitsnetz" in Form von Integrationstests erstellen.
2. Sicheres Refactoring: Die Strategie "Mauer verschieben"
Die klassische Technik für C# ist das Strangler Fig Pattern (Würgefeigen-Muster). Anstatt alles auf einmal umzuschreiben, ersetzen Sie nach und nach alte Komponenten durch neue, bis der alte Code nicht mehr verwendet wird.
2.1. Schnittstellen extrahieren und Abhängigkeiten einfügen
Das häufigste Antipattern in Legacy-C# sind statische Hilfsmethoden. Betrachten wir ein Beispiel:
// Veralteter Codepublic class OrderService{ public void ProcessOrder(int orderId) { var conn = new SqlConnection("Server=myServer;Database=myDB;..."); var cmd = new SqlCommand("SELECT * FROM Orders WHERE Id = " + orderId, conn); // ... tonnenweise Logik }}Schritt 1: Schnittstelle für den Datenzugriff extrahieren.
public interface IOrderRepository{ Order GetById(int orderId);}
public class OrderRepository : IOrderRepository{ private readonly string _connectionString;
public OrderRepository(string connectionString) { _connectionString = connectionString; }
public Order GetById(int orderId) { using var conn = new SqlConnection(_connectionString); // ... sichere Abfrage }}Schritt 2: Abhängigkeit über den Konstruktor einfügen. Jetzt können Sie das Repository in Tests ersetzen, und der OrderService selbst ist nicht mehr von einer bestimmten Datenbankimplementierung abhängig.
public class OrderService{ private readonly IOrderRepository _orderRepository;
public OrderService(IOrderRepository orderRepository) { _orderRepository = orderRepository; }
public void ProcessOrder(int orderId) { var order = _orderRepository.GetById(orderId); // ... Geschäftslogik }}2.2. Veraltete Typen durch moderne ersetzen
Im .NET Framework wurden häufig ArrayList, Hashtable und
Blogs
Buchempfehlungen
Lesen Sie auch
Asynchrone Programmierung in Kotlin: Vollständiger Leitfaden in der Praxis
TypeScript: Vollständiger Leitfaden zur Vorbereitung auf das Vorstellungsgespräch
Clean Code auf Swift: Prinzipien und Praxisbeispiele
Testen in Java: Unit-, Integrations- und E2E-Tests – vollständiger Leitfaden
Kotlin-Bibliotheken, die Ihr Projekt im Jahr 2024 retten werden