Introduction : pourquoi le réusinage de code legacy en Kotlin n'est pas qu'une simple réécriture
Travailler avec du code legacy en Kotlin n'est pas simplement un remplacement mécanique des constructions Java par la syntaxe du langage. C'est un processus stratégique visant à améliorer la lisibilité, la sécurité et les performances du code sans modifier son comportement externe. Dans cet article, nous examinerons des techniques avancées de réusinage qui vous aideront à transformer un « zoo » chaotique de classes en un système cohérent et maintenable. Nous n'aborderons pas les bases comme var vs val — nous nous concentrerons sur ce qui change réellement l'architecture.
1. Migration depuis Java : remplacement des getters, setters et classes utilitaires
Le problème le plus courant du code legacy est l'abondance de classes Java avec des getters et setters explicites, ainsi que des classes utilitaires statiques. Kotlin propose des solutions élégantes.
1.1. Accès basé sur les propriétés au lieu des JavaBeans
Au lieu d'écrire user.getName() et user.setName("Alice"), utilisez les propriétés Kotlin. Ce n'est pas qu'un sucre syntaxique : vous obtenez une interface d'accès unifiée qui peut être redéfinie.
// Java legacypublic class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; }}
// Kotlin refactorisédata class User(val name: String) // Ou avec var si la mutabilité est nécessaireSi la logique du getter/setter est plus complexe (par exemple, validation), utilisez des accesseurs personnalisés :
class User { var name: String = "" set(value) { field = value.trim() // Suppression automatique des espaces }}1.2. Fonctions d'extension au lieu d'utilitaires statiques
Le code legacy contient souvent des classes comme StringUtils ou FileUtils. En Kotlin, elles sont remplacées par des fonctions d'extension. Cela rend le code plus lisible et le rapproche du style « méthode d'objet ».
// Java : StringUtils.isNullOrEmpty(str)// Kotlin : str.isNullOrEmpty() (déjà dans stdlib)
// Exemple de fonction d'extension personnaliséefun String.isValidEmail(): Boolean { return this.contains("@") && this.contains(".")}
// Utilisationval email = "user@example.com"if (email.isValidEmail()) { ... }2. Travail avec null safety : remplacement d'Optional et des vérifications de null
Le code Java legacy souffre souvent de NullPointerException. Kotlin résout ce problème au niveau des types, mais lors du réusinage, il faut remplacer soigneusement Optional et les chaînes de vérifications de null.
2.1. Remplacement d'Optional par des types nullables
Au lieu de Optional<String>, utilisez String?. Cela réduit la surcharge liée à la création d'objet et rend le code plus concis.
// Javapublic Optional<String> findName() { ... }
// Kotlinfun findName(): String? { ... }Pour les chaînes d'appels, utilisez l'opérateur ?. et l'opérateur Elvis ?: :
// Au lieu de if (user != null && user.address != null) ...val city = user?.address?.city ?: "Inconnu"2.2. Utilisation de require() et check() pour les invariants
Au lieu de vérifications manuelles au début des fonctions, utilisez les fonctions intégrées :
fun processUser(user: User?) { requireNotNull(user) { "L'utilisateur ne doit pas être null" } // ou require(user != null) { ... } // Travail ultérieur avec user (smart cast)}3. Réusinage de la logique conditionnelle : classes sealed et when
D'énormes blocs if-else ou switch sont un signe de code legacy. En Kotlin, ils peuvent être remplacés par sealed class et un when exhaustif.
3.1. Remplacement d'un enum avec champs par une classe sealed
Si vous avez un enum qui stocke différents types de données pour chaque valeur