Einleitung: Warum Legacy-Code kein Todesurteil ist
\nJeder 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.
\nIn 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\n1. Diagnose: Wie man das Ausmaß von Legacy-Code bewertet
\nBevor 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
varanstelle vonval, mutable Listen, die zwischen Modulen übergeben werden. \n - Null-Albtraum – Der
!!-Operator, der über den gesamten Code verstreut ist, und endloseif (x != null)-Prüfungen. \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\n1.1. Erstellung einer Abhängigkeitskarte
\nNehmen 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\n2. Refactoring-Strategie: Schritt-für-Schritt-Aktionsplan
\nRefactoring ohne Plan führt in die Katastrophe. Verwenden Sie den Ansatz „Technik der kleinen Schritte“ (Baby Steps). Der Aktionsablauf:
\n- \n
- Dokumentieren Sie das Verhalten – Schreiben Sie Charakterisierungstests (characterization tests), die das aktuelle Systemverhalten festhalten. \n
- Isolieren Sie Änderungen – Arbeiten Sie in einem separaten Branch, verwenden Sie Feature Toggles für komplexe Bereiche. \n
- Refactoring nach Mustern – Wenden Sie bewährte Techniken an (Extract Method, Replace Conditional with Polymorphism, Introduce Parameter Object). \n
- Schrittweise Migration – Versuchen Sie nicht, alles auf einmal umzuschreiben. Verwenden Sie die
@Deprecated-Annotation für alte Methoden. \n
2.1. Beispiel: Ersetzen von if-else durch when und sealed classes
\nTypischer 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}\nRefactoring 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}\nDies eliminiert die Notwendigkeit für else und macht den Code erweiterbar.
3. Sicheres Arbeiten mit nullable und Collections
\nLegacy-Code enthält oft