Introduction : Pourquoi le « code propre » ne concerne pas l'esthétique
Quand vous commencez tout juste à écrire en Kotlin, il semble que le langage lui-même résolve déjà la moitié des problèmes. Pas de code passe-partout comme en Java. Il y a `data class`, les lambdas, les coroutines. Mais si vous écrivez du code « au fil de l'eau », dans un mois vous ne pourrez plus vous y retrouver vous-même. Et si une équipe travaille sur le projet, vos collègues vous détesteront (ou vous les détesterez).
Le code propre en Kotlin ne concerne pas les tabulations contre les espaces. Il s'agit de réduire la charge cognitive, de rendre la logique transparente et d'éviter les bugs qui se cachent dans les constructions « pratiques ». Dans cet article, je vais montrer des techniques concrètes : du travail avec les nullable à l'utilisation des scope functions et des sealed classes. Sans blabla, que de la pratique.
1. Null Safety : comment ne pas multiplier les « ? » et « !! »
Kotlin est fier de son système de types, mais beaucoup écrivent comme si c'était Java avec du « sucre syntaxique ». Le principal anti-patron est les chaînes `?.let` et les `!!` paniques.
Mauvais (lisibilité compromise) :
user?.address?.city?.let { city -> println(city)} ?: run { println("Pas de ville")}Bon (élégant et propre) :
val city = user?.address?.cityprintln(city ?: "Pas de ville")Utilisez `!!` seulement si vous êtes sûr à 100% que la valeur n'est pas null (par exemple, dans les tests ou après une vérification dans `if`). En production, c'est une grenade. Si vous devez convertir un nullable en non-null et traiter les deux cas, utilisez when ou l'opérateur Elvis.
2. Scope Functions : `apply`, `run`, `with`, `let`, `also` — quand utiliser quoi ?
C'est un outil puissant, mais on en abuse souvent. La règle d'or : n'imbriquez pas les scope functions les unes dans les autres. Si vous avez `list.apply { ... }.let { ... }`, vous écrivez des spaghettis.
Règles de choix :
apply— pour la configuration d'un objet (retourne l'objet lui-même). Idéal pour les builders.let— pour exécuter une action avec une valeur non-null (souvent en paire avec `?.`).run— quand vous devez calculer une valeur et retourner un résultat.with— pour regrouper les appels d'un même objet sans retour.also— pour les effets secondaires (journalisation, validation).
Exemple propre :
data class User(var name: String, var age: Int)
fun createUser(): User = User("Inconnu", 0).apply { name = "Alice" age = 30}N'écrivez pas user?.let { it.name } — c'est redondant. Utilisez simplement user?.name.
3. Extension Functions : ne multipliez pas les classes utilitaires
En Java, vous auriez écrit StringUtils.capitalize(str). En Kotlin — str.capitalize(). Les fonctions d'extension sont un moyen de « compléter » une classe existante sans l'hériter.
Exemple d'extension utile :
fun String.isValidEmail(): Boolean { return this.contains("@") && this.contains(".")}Mais n'en faites pas trop. Si une fonction d'extension n'est utilisée qu'à un seul endroit, elle devrait probablement être privée. Et ne créez pas d'extension sur `Any` — cela casse le système de types.
4. Sealed Classes et When : votre principal outil à la place de if-else
Quand vous avez un ensemble fini d'états (par exemple, le résultat d'une requête API), utilisez sealed class. Cela garantit que vous avez traité toutes les variantes.
Approche correcte :
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("Données : ${result.data}") is NetworkResult.Error -> println("Erreur : ${resu