Refactoring von Legacy-Code in Kotlin: Schritt-für-Schritt-Anleitung

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

Refactoring von Legacy-Code in Kotlin: Strategie und praktische Beispiele



Die Arbeit mit Legacy-Code ist ein unvermeidlicher Teil des Lebens eines jeden Entwicklers. Oft ist solcher Code in Java geschrieben, wurde aber ohne tiefgreifendes Umdenken nach Kotlin migriert oder stellt "Spaghetti" aus Utility-Funktionen und nullable-Feldern dar. Das Refactoring solchen Codes verbessert nicht nur die Lesbarkeit, sondern steigert auch die Leistung und Sicherheit der Anwendung. In diesem Artikel werden wir eine schrittweise Strategie zum Refactoring von Legacy-Code in Kotlin mit realen Beispielen durchgehen.



1. Vorbereitung und Testen: das Fundament der Sicherheit



Bevor Sie Code ändern, müssen Sie dessen korrekte Funktion nach den Änderungen sicherstellen. Ohne Tests wird Refactoring zum Ratespiel.



  • Schreiben Sie Charakterisierungstests (Characterization Tests): Führen Sie den vorhandenen Code mit verschiedenen Eingabedaten aus und halten Sie die Ausgabewerte fest. Diese Tests werden Ihr "Sicherheitsnetz".
  • Verwenden Sie die @Deprecated-Annotation: Wenn Sie die alte öffentliche API nicht sofort entfernen können, markieren Sie sie mit @Deprecated und geben Sie eine Alternative an.
  • Arbeiten Sie in kleinen Schritten: Refactoren Sie niemals mehr als einen logischen Block auf einmal. Jeder Commit muss alle Tests bestehen.


Beispiel für einen Charakterisierungstest für eine alte Funktion:



// Legacy-Codefun calculateTotal(items: List<Int>?): Int {    if (items == null) return 0    var sum = 0    for (item in items) {        sum += item    }    return sum}

// Test@Testfun `calculateTotal returns sum or zero`() { assertEquals(0, calculateTotal(null)) assertEquals(6, calculateTotal(listOf(1, 2, 3))) assertEquals(0, calculateTotal(emptyList()))}


2. Beseitigung von Null-Sicherheitsproblemen und Verwendung von Scope-Funktionen



Legacy-Code leidet oft unter übermäßigen Null-Prüfungen. Kotlin bietet elegante Werkzeuge für die Arbeit mit nullable-Typen.



Was wir tun: Ersetzen von verschachtelten if (x != null) durch let, run oder also.



// Vor dem Refactoringfun processUser(user: User?) {    if (user != null) {        if (user.name != null) {            println("User: ${user.name}")        }    }}

// Nach dem Refactoringfun processUser(user: User?) { user?.let { u -> u.name?.let { name -> println("User: $name") } }}

// Oder noch kürzer mit runfun processUserShort(user: User?) { user?.run { name?.let { println("User: $it") } }}


Dieser Ansatz macht den Code deklarativer und verringert die Wahrscheinlichkeit, eine Prüfung zu übersehen.



3. Ersetzen von Utility-Klassen durch Extension-Funktionen



In Legacy-Java-Projekten findet man oft statische Utility-Klassen (StringUtils, DateUtils). In Kotlin sollten diese besser durch Extension-Funktionen ersetzt werden.



Warum das wichtig ist: Extension-Funktionen machen den Code lesbarer und ermöglichen der IDE, sie als Methoden der Klasse selbst vorzuschlagen.



// Legacy-Java-Stilobject StringUtils {    fun isPalindrome(input: String): Boolean {        val cleaned = input.filter { it.isLetterOrDigit() }.lowercase()        return cleaned == cleaned.reversed()    }}

// Verwendungval result = StringUtils.isPalindrome("A man, a plan, a canal: Panama")

// Refactoring auf Kotlinfun String.isPalindrome(): Boolean { val cleaned = this.filter { it.isLetterOrDigit() }.lowercase() return cleaned == cleaned.reversed()}

// Verwendungval result = "A man, a plan, a canal: Panama".isPalindrome()


Es lohnt sich auch, auf den Ersatz von java.util.Date durch java.time (LocalDate, Instant) zu achten – das erhöht die Sicherheit bei der Arbeit mit Zeitangaben.



4. Optimiz

Blogs

Buchempfehlungen