Refactoring von Legacy-Code in Kotlin: Von A bis Z

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: Warum Legacy-Code kein Todesurteil ist

\n

Jeder Entwickler ist schon einmal auf Legacy-Code gestoßen – diese „Black Box“, die man nicht anzufassen wagt, weil „es funktioniert und man nicht daran herumfummeln sollte“. Wenn jedoch die Codebasis in Kotlin (oder Java, das nach und nach migriert wird) die Entwicklung ausbremst, Fehler anhäuft und neue Teammitglieder abschreckt, ist die Zeit für ein Refactoring gekommen. Das Refactoring von Legacy-Code in Kotlin ist nicht einfach nur Umschreiben, sondern ein systematischer Prozess zur Verbesserung der internen Struktur ohne Änderung des externen Verhaltens.

\n

In diesem Artikel gehen wir den gesamten Weg durch: von der Analyse des aktuellen Zustands bis zur Implementierung moderner Kotlin-Praktiken. Sie erfahren, wie Sie veralteten Code sicher umwandeln können, indem Sie die Sprachfunktionen (Nullsicherheit, Extension Functions, Sealed Classes) nutzen, ohne die Produktion zu gefährden.

\n\n

1. Diagnose: Wie man das Ausmaß von Legacy-Code bewertet

\n

Bevor Sie mit dem Refactoring beginnen, müssen Sie verstehen, womit Sie es zu tun haben. Legacy-Code in Kotlin stammt oft aus Projekten, die ursprünglich in Java geschrieben und dann teilweise migriert wurden. Typische Anzeichen sind:

\n
    \n
  • God-Objekte – Klassen, die alles machen: vom Parsen bis zur Geschäftslogik.
  • \n
  • Fehlende Tests – Der Code ist nicht mit Unit-Tests abgedeckt, und jede Änderung ist riskant.
  • \n
  • Mutable Daten – Übermäßige Verwendung von var anstelle von val, mutable Listen, die zwischen Modulen übergeben werden.
  • \n
  • Null-Albtraum – Der !!-Operator, der über den gesamten Code verstreut ist, und endlose if (x != null)-Prüfungen.
  • \n
\n

Für die Diagnose verwenden Sie Tools: Detekt (statische Analyse für Kotlin), SonarQube (Qualitätsmetriken) und IntelliJ IDEA Inspections. Erstellen Sie eine Checkliste der Problemstellen und priorisieren Sie diese nach Risiko und Nutzungshäufigkeit.

\n\n

1.1. Erstellung einer Abhängigkeitskarte

\n

Nehmen Sie ein Blatt Papier oder verwenden Sie PlantUML, um die Beziehungen zwischen den Klassen zu visualisieren. Wenn der Abhängigkeitsgraph wie ein Spinnennetz aussieht, ist das ein Warnsignal. Achten Sie besonders auf zyklische Abhängigkeiten: Sie machen den Code zerbrechlich und schwer testbar.

\n\n

2. Refactoring-Strategie: Schritt-für-Schritt-Aktionsplan

\n

Refactoring ohne Plan führt in die Katastrophe. Verwenden Sie den Ansatz „Technik der kleinen Schritte“ (Baby Steps). Der Aktionsablauf:

\n
    \n
  1. Dokumentieren Sie das Verhalten – Schreiben Sie Charakterisierungstests (characterization tests), die das aktuelle Systemverhalten festhalten.
  2. \n
  3. Isolieren Sie Änderungen – Arbeiten Sie in einem separaten Branch, verwenden Sie Feature Toggles für komplexe Bereiche.
  4. \n
  5. Refactoring nach Mustern – Wenden Sie bewährte Techniken an (Extract Method, Replace Conditional with Polymorphism, Introduce Parameter Object).
  6. \n
  7. Schrittweise Migration – Versuchen Sie nicht, alles auf einmal umzuschreiben. Verwenden Sie die @Deprecated-Annotation für alte Methoden.
  8. \n
\n\n

2.1. Beispiel: Ersetzen von if-else durch when und sealed classes

\n

Typischer Legacy-Code in Kotlin enthält if-else-Ketten zur Zustandsverarbeitung:

\n
// Schlecht: mutable Flag und if-else\nvar status = "active"\nif (status == "active") {\n    // ...\n} else if (status == "inactive") {\n    // ...\n} else {\n    throw Exception("Unknown status")\n}
\n

Refactoring mit sealed classes:

\n
// Gut: typsicheres Modell\nsealed class Status {\n    object Active : Status()\n    object Inactive : Status()\n    data class Custom(val reason: String) : Status()\n}\n\nfun handleStatus(status: Status) = when (status) {\n    is Status.Active -> // Verarbeitung\n    is Status.Inactive -> // Verarbeitung\n    is Status.Custom -> // Verarbeitung mit reason\n}
\n

Dies eliminiert die Notwendigkeit für else und macht den Code erweiterbar.

\n\n

3. Sicheres Arbeiten mit nullable und Collections

\n

Legacy-Code enthält oft

Blogs

Buchempfehlungen