Refactoring von Legacy-Code in Kotlin: Strategie und Beispiele
Die Arbeit mit Legacy-Code ist ein unvermeidlicher Teil des Lebens eines jeden Entwicklers. Oft ist solcher Code in Java geschrieben, aber selbst Projekte mit frühen Kotlin-Versionen können „Code Smells“ und architektonische Fehler enthalten. In diesem Artikel werden wir eine schrittweise Strategie zum Refactoring von Legacy-Code in Kotlin durchgehen, konkrete Beispiele für Transformationen geben und erklären, wie man typische Fehler vermeidet.
1. Vorbereitung auf das Refactoring: Sicherheit geht vor
Der erste und wichtigste Schritt ist die Sicherstellung der Änderungssicherheit. Ohne eine gute Testabdeckung wird jedes Refactoring zu einem russischen Roulette.
- Fügen Sie Modultests hinzu. Wenn es keine Tests gibt, schreiben Sie sie für kritische Codeabschnitte. Verwenden Sie die Frameworks
JUnit 5undMockKfür Kotlin. - Frieren Sie das Verhalten ein. Schreiben Sie Tests, die das aktuelle Systemverhalten überprüfen (Characterization Tests), selbst wenn es falsch erscheint. Dies ermöglicht es, die Logik beim Refactoring nicht zu beschädigen.
- Verwenden Sie ein Versionskontrollsystem. Machen Sie Commits nach jedem logisch abgeschlossenen Schritt, um zurückrollen zu können.
Beispiel eines einfachen Tests für eine Legacy-Funktion in Kotlin:
import org.junit.jupiter.api.Testimport kotlin.test.assertEquals
class LegacyServiceTest { @Test fun `test calculateTotal`() { val service = LegacyService() val result = service.calculateTotal(listOf(10.0, 20.0, 30.0)) assertEquals(60.0, result) }}2. Schrittweise Migration von Java zu Kotlin
Wenn Ihr Legacy-Code in Java geschrieben ist, versuchen Sie nicht, alles auf einmal umzuschreiben. Verwenden Sie automatische Konverter (z. B. den in IntelliJ IDEA integrierten: Code → Convert Java File to Kotlin File) und verbessern Sie dann das Ergebnis manuell.
Die wichtigsten Punkte, auf die Sie nach der Konvertierung achten sollten:
- Null safety. Ersetzen Sie
@Nullableund@NonNulldurch die Operatoren?und!!(letzteren nur, wenn Sie sicher sind, dass kein null vorhanden ist). - Data-Klassen. Verwenden Sie anstelle von Java-Klassen mit
equals()undhashCode()diedata class. - Extension-Funktionen. Lagern Sie utilitaristische statische Methoden in Extension Functions aus.
Beispiel für die Umwandlung einer Java-Klasse in Kotlin:
// Javapublic class User { private String name; private int age;
public User(String name, int age) { this.name = name; this.age = age; }
public String getName() { return name; } public int getAge() { return age; }}
// Kotlin (nach dem Refactoring)data class User(val name: String, val age: Int)3. Verbesserung der Architektur: Spaghetti-Code loswerden
Legacy-Code leidet oft unter Verstößen gegen die SOLID-Prinzipien. Hier sind drei konkrete Techniken, die helfen, den Code in Ordnung zu bringen:
3.1. Trennung der Verantwortlichkeiten (Single Responsibility)
Finden Sie Klassen, die zu viel tun. Zum Beispiel ein „God Object“, das Daten speichert, validiert und Anfragen sendet. Teilen Sie es in mehrere Klassen mit klaren Verantwortungsbereichen auf.
3.2. Ersetzen langer if-else-Ketten durch when
Kotlin bietet den mächtigen when-Operator, der den Code lesbarer macht:
// Vor dem Refactoringfun getStatusText(status: Int): String { if (status == 0) return "Pending" else if (status == 1) return "Active" else if (status == 2) return "Blocked" else return "Unknown"}
// Nach dem Refactoringfun getStatusText(status: Int): String = when (status) { 0 -> "Pending" 1 -> "Active" 2 -> "Blocked" else -> "Unknown"}