Adopter Kotlin pour les grandes équipes

Migrer vers un nouveau langage de programmation peut représenter un défi de taille. Le secret de la réussite consiste à commencer lentement, à procéder par étapes et à effectuer des tests fréquents pour bien préparer votre équipe. Kotlin facilite la migration, car il se compile en bytecode JVM et est entièrement interopérable avec Java.

Former votre équipe

La première étape avant de procéder à la migration consiste à établir une compréhension de base commune au sein de votre équipe. Voici quelques conseils qui pourraient vous être utiles pour accélérer ce processus.

Groupes d'étude

Les groupes d'étude sont un moyen efficace de faciliter l'apprentissage et la mémorisation des informations. Des études suggèrent que le fait de réciter ce que vous avez appris en groupe permet de renforcer le cours. Fournissez un livre sur Kotlin ou un autre support d'étude à chaque membre du groupe et demandez au groupe d'étudier quelques chapitres par semaine. Lors de chaque réunion, les membres du groupe doivent comparer ce qu'ils ont appris et partager leurs questions et observations.

Développer une culture de l'apprentissage

Tout le monde peut enseigner. Que ce soit un responsable des technologies, un chef d'équipe ou un contributeur individuel, tous peuvent favoriser un environnement d'apprentissage efficace propice à la réussite. Une façon de faciliter ce processus est d'organiser des réunions régulières au cours desquelles un membre de l'équipe est désigné pour présenter ce qu'il a appris ou ce qu'il souhaite partager. Vous pouvez mobiliser votre groupe d'étude en demandant à des volontaires de présenter un nouveau chapitre chaque semaine jusqu'à ce que l'équipe soit familiarisée avec le nouveau langage.

Désigner un champion

Enfin, désignez un champion pour mener à bien ce projet d'apprentissage. Cette personne peut agir en tant qu'expert lorsque vous commencez le processus de migration. Il est important de l'inclure dans toutes vos réunions de travail liées à Kotlin. Idéalement, cette personne est déjà passionnée par Kotlin et possède des connaissances pratiques.

Commencer lentement

Il est essentiel de commencer lentement et de définir de façon stratégique les parties de votre écosystème à modifier en premier. Souvent, il est préférable de vous attaquer à une seule application au sein de votre organisation en évitant de commencer par une application phare. En termes de migration de l'application choisie, chaque situation est différente, mais voici quelques points de départ courants :

Modèle de données

Votre modèle de données comprend probablement de nombreuses informations d'état ainsi que quelques méthodes, y compris des méthodes courantes telles que toString(), equals() et hashcode(). Ces méthodes peuvent généralement être migrées et passer des tests unitaires facilement de façon isolée.

Prenons l'exemple de l'extrait de code Java suivant :

public class Person {

   private String firstName;
   private String lastName;
   // ...

   public String getFirstName() {
       return firstName;
   }

   public void setFirstName(String firstName) {
       this.firstName = firstName;
   }

   public String getLastName() {
       return lastName;
   }

   public void setLastName(String lastName) {
       this.lastName = lastName;
   }

   @Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
       Person person = (Person) o;
       return Objects.equals(firstName, person.firstName) &&
               Objects.equals(lastName, person.lastName);
   }

   @Override
   public int hashCode() {
       return Objects.hash(firstName, lastName);
   }

   @Override
   public String toString() {
       return "Person{" +
               "firstName='" + firstName + '\'' +
               ", lastName='" + lastName + '\'' +
               '}';
   }
}

Vous pouvez remplacer la classe Java par une seule ligne de code Kotlin, comme illustré ci-dessous :

data class Person(var firstName: String?, var lastName : String?)

Vous pouvez ensuite tester ce code avec votre suite de tests actuelle. L'idée est de commencer petit avec un modèle à la fois et des classes de transition qui sont principalement des états et non des comportements. Veillez à effectuer régulièrement des tests.

Migrer les tests

Vous pouvez également commencer par convertir les tests existants et écrire de nouveaux tests en Kotlin. Cela peut donner à votre équipe le temps de se familiariser avec le langage avant d'écrire le code à intégrer à votre application.

Migrer des méthodes utilitaires vers des fonctions d'extension

Toutes les classes utilitaires statiques (StringUtils, IntegerUtils, DateUtils, YourCustomTypeUtils, etc.) peuvent être représentées par des fonctions d'extension Kotlin et utilisées par votre codebase Java existant.

Supposons par exemple que vous ayez une classe StringUtils avec quelques méthodes :

package com.java.project;

public class StringUtils {

   public static String foo(String receiver) {
       return receiver...;  // Transform the receiver in some way
   }

   public static String bar(String receiver) {
       return receiver...;  // Transform the receiver in some way
   }

}

Ces méthodes peuvent ensuite être utilisées ailleurs dans votre application, comme illustré dans l'exemple suivant :

...

String myString = ...
String fooString = StringUtils.foo(myString);

...

En utilisant les fonctions d'extension Kotlin, vous pouvez fournir la même interface Utils aux appelants Java tout en offrant une API plus concise pour votre code base Kotlin en pleine expansion.

Pour ce faire, vous pouvez commencer par convertir cette classe Utils en Kotlin à l'aide de la conversion automatique fournie par l'IDE. Le résultat peut ressembler à ceci :

package com.java.project

object StringUtils {

   fun foo(receiver: String): String {
       return receiver...;  // Transform the receiver in some way
   }

   fun bar(receiver: String): String {
       return receiver...;  // Transform the receiver in some way
   }

}

Ensuite, supprimez la définition de la classe ou de l'objet, préfixez chaque nom de fonction avec le type sur lequel cette fonction doit s'appliquer et utilisez-le pour référencer le type à l'intérieur de la fonction, comme illustré dans l'exemple suivant :

package com.java.project

fun String.foo(): String {
    return this...;  // Transform the receiver in some way
}

fun String.bar(): String {
    return this...;  // Transform the receiver in some way
}

Enfin, ajoutez une annotation JvmName en haut du fichier source pour rendre le nom compilé compatible avec le reste de votre application, comme illustré dans l'exemple suivant :

@file:JvmName("StringUtils")
package com.java.project
...

La version finale doit ressembler à ceci :

@file:JvmName("StringUtils")
package com.java.project

fun String.foo(): String {
    return this...;  // Transform `this` string in some way
}

fun String.bar(): String {
    return this...;  // Transform `this` string in some way
}

Notez que ces fonctions peuvent désormais être appelées en utilisant Java ou Kotlin avec les conventions qui correspondent à chaque langage.

Kotlin

...
val myString: String = ...
val fooString = myString.foo()
...

Java

...
String myString = ...
String fooString = StringUtils.foo(myString);
...

Terminer la migration

Une fois que votre équipe est familiarisée avec Kotlin et que vous avez migré de petites parties, vous pouvez vous attaquer à des composants plus importants tels que les fragments, les activités, les objets ViewModel et d'autres classes liées à la logique métier.

Points à prendre en compte

Tout comme Java, Kotlin peut s'écrire dans un style idiomatique qui contribue à sa concision. Cependant, il se peut qu'au début, vous trouviez que le code Kotlin produit par votre équipe ressemble davantage au code Java qu'il remplace. Cela évoluera à mesure que votre équipe se familiarisera avec Kotlin. N'oubliez pas que le changement progressif est la clé du succès.

Voici quelques actions que vous pouvez effectuer pour assurer la cohérence de votre code base Kotlin à mesure qu'il se développe :

Normes de codage courantes

Veillez à définir un ensemble standard de conventions de codage dès le début de votre processus de migration. Si vous le trouvez opportun, vous pouvez vous écarter du Guide de style Kotlin pour Android.

Outils d'analyse statique

Faites respecter les normes de codage définies pour votre équipe en utilisant Android Lint et d'autres outils d'analyse statique. klint, un linter Kotlin tiers, fournit également des règles supplémentaires pour Kotlin.

Intégration continue

Veillez à appliquer les normes de codage courantes et à fournir une couverture de test suffisante pour votre code Kotlin. L'intégration de ces éléments dans un processus de compilation automatisé peut contribuer à la cohérence et au respect de ces normes.

Interopérabilité

Dans la plupart des cas, Kotlin interagit de manière transparente. Toutefois, veuillez noter les points suivants :

Possibilité de valeur nulle

Kotlin s'appuie sur les annotations de possibilité de valeur nulle dans le code compilé pour la déduire du côté Kotlin. Si ces annotations ne sont pas fournies, Kotlin utilise par défaut un type de plate-forme pouvant être considéré comme de type nullable ou non nullable. Cependant, si l'on n'y prend garde, cela peut générer des erreurs NullPointerException .

Adopter de nouvelles fonctionnalités

Kotlin fournit de nombreuses nouvelles bibliothèques et du sucre syntaxique pour réduire le code récurrent, ce qui accélère le développement. Cela dit, soyez prudent et méthodique lorsque vous utilisez les fonctions de bibliothèque standards de Kotlin, telles que les fonctions de collecte, les coroutines et les lambdas.

Voici un problème très courant que rencontrent les développeurs Kotlin débutants. Prenons le code Kotlin suivant :

val nullableFoo: Foo? = ...

// This lambda executes only if nullableFoo is not null
// and `foo` is of the non-nullable Foo type
nullableFoo?.let { foo ->
   foo.baz()
   foo.zap()
}

L'intent dans cet exemple est d'exécuter foo.baz() et foo.zap() si nullableFoo n'est pas nul, évitant ainsi une erreur NullPointerException. Bien que ce code fonctionne comme prévu, il est moins intuitif à lire qu'une simple vérification de valeur nulle et une diffusion intelligente, comme illustré dans l'exemple suivant :

val nullableFoo: Foo? = null
if (nullableFoo != null) {
    nullableFoo.baz() // Using !! or ?. isn't required; the Kotlin compiler infers non-nullability
    nullableFoo.zap() // from guard condition; smart casts nullableFoo to Foo inside this block
}

Tests

Par défaut, les classes et leurs fonctions sont fermées pour les extensions en Kotlin. Vous devez ouvrir explicitement celles que vous souhaitez sous-classer. Ce comportement est une décision de conception du langage dans le but de promouvoir la composition plutôt que l'héritage. Kotlin permet l'implémentation d'un comportement par délégation afin de simplifier la composition.

Ce comportement pose un problème pour les cadres de simulation, tels que Mockito, qui s'appuient sur l'implémentation d'interfaces ou l'héritage pour remplacer les comportements pendant les tests. Pour les tests unitaires, vous pouvez activer l'utilisation de la fonctionnalité Mock Maker Inline de Mockito, qui vous permet de simuler les classes et méthodes finales. Vous pouvez également utiliser le plug-in de compilation All-Open pour ouvrir une classe Kotlin et ses membres que vous souhaitez tester dans le cadre du processus de compilation. Son principal avantage est qu'il fonctionne avec des tests unitaires et d'instrumentation.

Plus d'informations

Pour en savoir plus sur l'utilisation de Kotlin, consultez les liens suivants :