Einleitung: Warum „sauberer Code“ nichts mit Ästhetik zu tun hat
Wenn Sie gerade erst anfangen, in Kotlin zu schreiben, scheint es, als ob die Sprache selbst bereits die Hälfte der Probleme löst. Es gibt kein Boilerplate wie in Java. Es gibt `data class`, Lambdas, Coroutinen. Aber wenn Sie Code einfach „wie er kommt“ hinschmeißen, werden Sie sich in einem Monat selbst nicht mehr darin zurechtfinden. Und wenn ein Team an dem Projekt arbeitet, werden Ihre Kollegen Sie hassen (oder Sie sie).
Sauberer Code in Kotlin geht nicht um Tabs statt Leerzeichen. Es geht darum, die kognitive Belastung zu reduzieren, die Logik transparent zu machen und Fehler zu vermeiden, die sich in „praktischen“ Konstrukten verstecken. In diesem Artikel zeige ich konkrete Techniken: vom Umgang mit Nullable bis zur Verwendung von Scope Functions und Sealed Classes. Ohne überflüssiges Beiwerk, nur Praxis.
1. Null Safety: Wie man „?“ und „!!“ nicht vermehrt
Kotlin ist stolz auf sein Typsystem, aber viele schreiben so, als wäre es Java mit „syntaktischem Zucker“. Das Hauptantipattern sind Ketten von `?.let` und panische `!!`.
Schlecht (Lesbarkeit leidet):
user?.address?.city?.let { city -> println(city)} ?: run { println("Keine Stadt")}Gut (elegant und sauber):
val city = user?.address?.cityprintln(city ?: "Keine Stadt")Verwenden Sie `!!` nur, wenn Sie zu 100% sicher sind, dass der Wert nicht null ist (z.B. in Tests oder nach einer Prüfung mit `if`). In der Produktion ist das eine Granate. Wenn Sie einen Nullable in einen Non-Nullable umwandeln und beide Fälle behandeln müssen, verwenden Sie when oder den Elvis-Operator.
2. Scope Functions: `apply`, `run`, `with`, `let`, `also` – wann was?
Dies ist ein mächtiges Werkzeug, aber es wird oft missbraucht. Die goldene Regel: Verschachteln Sie Scope Functions nicht ineinander. Wenn Sie `list.apply { ... }.let { ... }` haben, schreiben Sie Spaghetti-Code.
Auswahlregeln:
apply– zur Konfiguration eines Objekts (gibt das Objekt selbst zurück). Ideal für Builder.let– um eine Aktion mit einem Non-Null-Wert auszuführen (oft in Kombination mit `?.`).run– wenn ein Wert berechnet und das Ergebnis zurückgegeben werden soll.with– um Aufrufe eines Objekts ohne Rückgabe zu gruppieren.also– für Nebeneffekte (Logging, Validierung).
Sauberes Beispiel:
data class User(var name: String, var age: Int)
fun createUser(): User = User("Unbekannt", 0).apply { name = "Alice" age = 30}Schreiben Sie nicht user?.let { it.name } – das ist überflüssig. Einfach user?.name.
3. Extension Functions: Vermeiden Sie unnötige Utility-Klassen
In Java würden Sie StringUtils.capitalize(str) schreiben. In Kotlin – str.capitalize(). Extension Functions sind eine Möglichkeit, eine fremde Klasse zu „erweitern“, ohne sie zu vererben.
Beispiel einer nützlichen Extension:
fun String.isValidEmail(): Boolean { return this.contains("@") && this.contains(".")}Übertreiben Sie es aber nicht. Wenn eine Extension-Funktion nur an einer Stelle verwendet wird, sollte sie vielleicht privat sein. Und machen Sie keine Extension auf `Any` – das zerstört das Typsystem.
4. Sealed Classes und When: Ihr wichtigstes Werkzeug statt if-else
Wenn Sie eine endliche Menge von Zuständen haben (z.B. das Ergebnis eines API-Aufrufs), verwenden Sie sealed class. Dies stellt sicher, dass Sie alle Varianten behandelt haben.
Der richtige Ansatz:
sealed class NetworkResult<out T> { data class Success<T>(val data: T) : NetworkResult<T>() data class Error(val message: String, val code: Int) : NetworkResult<Nothing>() object Loading : NetworkResult<Nothing>()}
fun handle(result: NetworkResult<String>) { when (result) { is NetworkResult.Success -> println("Daten: ${result.data}") is NetworkResult.Error -> println("Fehler: ${resu