Refactoring von Legacy-Code in C#: Fortgeschrittene Techniken und Praktiken

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einleitung: Wenn Code zu Legacy wird

Jeder C#-Entwickler, der in einem großen Unternehmen arbeitet, stößt früher oder später auf Projekte, die 5, 10 oder sogar 15 Jahre alt sind. Solcher Code wird als Legacy bezeichnet. Er funktioniert, bringt Geld, aber jede Änderung daran ist ein Risiko. Fehlende Tests, enge Kopplung (tight coupling), riesige Klassen mit Tausenden von Zeilen, magische Zeichenfolgen und Copy-Paste – das ist die Realität von Legacy.

Einfaches Refactoring „nach Augenmaß“ funktioniert hier nicht. Es sind fortgeschrittene Strategien erforderlich, die es ermöglichen, den Code schrittweise zu verbessern, ohne die Geschäftslogik zu beschädigen. In diesem Artikel behandeln wir 4 Schlüsselansätze für das Refactoring von Legacy-Systemen in C# (.NET) auf fortgeschrittenem Niveau.



1. Die „Strangler Fig“-Strategie: Austausch von Modulen ohne Systemstillstand

Das Strangler Fig-Muster (Würgefeige) ist ein Klassiker für die schrittweise Ablösung von Legacy-Komponenten. Der Kern: Sie schreiben nicht das gesamte System auf einmal neu, sondern fangen nach und nach Aufrufe an alte Module ab und leiten sie an neue weiter.



Umsetzung in C#

Der einfachste Weg ist die Verwendung von Schnittstellen und Fabriken. Angenommen, Sie haben eine alte Klasse OldOrderProcessor, die im gesamten Projekt verwendet wird.



// Alte Schnittstelle (bereits vorhanden)public interface IOrderProcessor{    void Process(Order order);}

// Alte Implementierung (Legacy)public class OldOrderProcessor : IOrderProcessor{ public void Process(Order order) { // Seit Jahren funktionierender, aber schmutziger Code }}


Erstellen Sie eine neue Implementierung und eine Fabrik mit Feature-Toggle:



public class NewOrderProcessor : IOrderProcessor{    public void Process(Order order)    {        // Neuer, sauberer Code    }}

public class OrderProcessorFactory{ public IOrderProcessor Create() { // Beispiel: Umschaltung per Konfiguration if (ConfigurationManager.AppSettings["UseNewProcessor"] == "true") return new NewOrderProcessor(); return new OldOrderProcessor(); }}


Jetzt können Sie den neuen Prozessor für 5% der Benutzer aktivieren, Fehler überwachen und den Prozentsatz schrittweise erhöhen. Wenn der alte Code nicht mehr benötigt wird, löschen Sie ihn.



2. Kernisolierung: Separation of Concerns durch Dependency Injection

Legacy-Code leidet oft unter God-Klassen und direkten Abhängigkeiten von Datenbank, Dateisystem oder externen APIs. Die Einführung von Dependency Injection (DI) ist der erste Schritt zur Modularität.



Praktisches Beispiel

Stellen Sie sich vor, Sie haben eine Klasse ReportGenerator, die intern eine SqlConnection erstellt:



public class ReportGenerator{    public void Generate()    {        using (var conn = new SqlConnection("..."))        {            // Abfragen        }    }}


Dies ist ohne eine echte Datenbank nicht testbar. Refactoring:



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(); // Berichtserstellung }}


Jetzt können Sie das Repository in Komponententests durch einen Mock ersetzen. Verwenden Sie den Standard-DI-Container von .NET Core (Microsoft.Extensions.DependencyInjection) oder Autofac zur Abhängigkeitsinjektion.



3. Testen von Legacy-Code: Golden Master und Characterization Tests

Ohne Tests ist Refactoring ein Sprung mit dem Fallschirm ohne Reservefallschirm. Für Legacy-Code eignet sich besonders der Ansatz der Characterization Tests (Charakterisierungstests) oder des Golden Master Testings.



Wie es funktioniert

Sie führen die

Blogs

Buchempfehlungen