De Java à Kotlin en 20 minutes
Tags: french, kotlin, java, beginners
Published: 2021-05-27 13:10:13

Cet article est dédié aux développeurs Java qui ont entendu parler de Kotlin, mais ne s'y sont encore jamais frottés. 

À quoi ressemblerait leur découverte de Kotlin ?

Une séance de mob-programming avec des collègues m'a permis d'en avoir une petite idée....

<!-- TOC -->
- [Show Me the Code!](#show-me-the-code)
- [Mob-programming](#mob-programming)
- [Configure Kotlin in project](#configure-kotlin-in-project)
- [Dire à Java que @ParametersAreNonnullByDefault](#dire-à-java-que-parametersarenonnullbydefault)
- [PinGuesser: Convert Java File to Kotlin File](#pinguesser-convert-java-file-to-kotlin-file)
- [PinGuesserTest: Convert Java File to Kotlin File - et corrections manuelles](#pinguessertest-convert-java-file-to-kotlin-file---et-corrections-manuelles)
- [Utiliser la librairie Kotlin standard](#utiliser-la-librairie-kotlin-standard)
- [Remplacer l'API Streams par la librairie Kotlin standard](#remplacer-lapi-streams-par-la-librairie-kotlin-standard)
- [Make val, not var](#make-val-not-var)
- [Fail fast](#fail-fast)
- [Passer à un style plus fonctionnel](#passer-à-un-style-plus-fonctionnel)
- [List.fold()](#listfold)
- [La suite?](#la-suite)

<!-- /TOC -->

## Show Me the Code !

Vous pouvez lire l'article, mais vous en tirerez davantage encore en suivant dans votre IDE l'évolution du code.

Pour cela, vous avez besoin de [IntelliJ Community Edition](https://www.jetbrains.com/idea). Qui est gratuit !

Sur MacOS par exemple, vous pouvez l'installer avec `$ brew install intellij-idea-ce`

Le repository se trouve ci-dessous, et les commits décrits sont présents [**dans cette pull-request**](https://github.com/jmfayard/from-java-to-kotlin/pull/1).

{% github https://github.com/jmfayard/from-java-to-kotlin %}

Mais tout d'abord un peu de contexte.

## Mob-programming

Mes collègues Sarah et Pierre sommes en train de faire avec moi une session [Mob programming](https://en.wikipedia.org/wiki/Mob_programming). 

Le but est de résoudre le [kata du PIN observé approximativement](https://www.codewars.com/kata/5263c6999e0f40dee200059d/train/java), dans lequel un espion moyennement fiable nous indique avoir vu le PIN **1357**. Mais il n'est pas très sûr de lui. Chaque chiffre pourrait être à la place un de ses voisins sur le pavé numérique. Le code pourrait donc être **1357** mais aussi par exemple **2357** ou **1368.**

Le projet est un projet Java/Maven. Il contient deux fichiers : `PinGuesser.java` et `PinGuesserTest.java`. Le projet a un temps de compilation et d'exécution des tests qui se comptent en secondes, pas en minutes comme dans beaucoup d'applications Android. Plus sympa en tant que développeur à mon avis.

Nous utilisons le plugin [Code With Me d'IntelliJ](https://www.jetbrains.com/help/idea/code-with-Moi.html) pour pouvoir faire du mob-programming.

Nous nous débrouillons bien : nous avons réussi à résoudre le Kata puis à le refactorer à un état satisfaisant. 

Mais il nous reste 20 minutes !

- _Sarah_ : Vous voyez quelque chose à améliorer ?
- _Pierre_ : Pas trop, ça m'a lair bien comme ça.
- _Moi_ : Il nous reste 20 minutes, pourquoi pas tout réécrire en Kotlin ?
- _Sarah_ : Oh, j'ai entendu parler de Kotlin, mais n'ai jamais eu l'occasion de l'essayer. Tout de même, 20 minutes, est-ce que c'est faisable ?
- _Moi_ : Lançons-nous, on verra bien ce que ça donne !

## Configure Kotlin in project

- _Pierre_ : Ok, moi je n'ai jamais fait de Kotlin de ma vie. Dis-moi quoi faire.
- _Moi_ : IntelliJ a une Action`Convert Java File to Kotlin File`. C'est une bonne manière de démarrer !
- _Pierre_ : Ok, j'essaye !

![https://user-images.githubusercontent.com/459464/118158571-42ef6000-b41c-11eb-89df-c32de3ffe8f0.png](https://user-images.githubusercontent.com/459464/118158571-42ef6000-b41c-11eb-89df-c32de3ffe8f0.png)


![https://user-images.githubusercontent.com/459464/118158602-4d115e80-b41c-11eb-8cb6-ee85143251ae.png](https://user-images.githubusercontent.com/459464/118158602-4d115e80-b41c-11eb-8cb6-ee85143251ae.png)

- _Pierre_ : IntelliJ me dit que Kotlin n'est pas configuré dans le projet. Il n'a pas tort !
- _Pierre_ : Comment je configure Kotlin dans un projet Maven ?
- _Moi_ : Je ne sais pas, j'ai toujours utilisé Gradle.
- _Moi_ : Laisse IntelliJ le faire !
- _Moi_ : Au passage, c'est équivalent à exécuter l'action `Tools > Kotlin > Configure Kotlin in project`
- _Pierre_ : Allons-y.
- _Pierre_ : Ça a l'air d'avoir marché, le fichier `pom.xml` a été modifié.
- _Pierre_ : [first commit](https://github.com/jmfayard/from-java-to-kotlin/pull/1/commits/caaab696fa50e57e5bcb7c0a23f70de29b43b480)

## Dire à Java que @ParametersAreNonnullByDefault

- _Moi_ : Avant d'essayer le convertisseur Java -> Kotlin, il y a une étape préalable. Comme vous  en avez peut-être entendu parler, Kotlin intègre la gestion de la nullabilité dans son système de type. Mais pas Java qui par défaut autorise `null` partout. Du coup le convertisseur autoriserait `null` partout lui aussi. Ce qui est techniquement correct, mais pas ce que nous voulons.
- _Sarah_ : Mais il y a des annotations en Java pour dire si la valeur `null` est autorisée ou non, pas vrai ?
- _Moi_ : Exactement, et l'annotation qui nous intéresse est `@ParametersAreNonnullByDefault` de la JSR 305. Elle s'applique à tout un package et informe que par défaut les paramètres sont non-nulls. Ça tombe bien, c'est exactement comme cela que ça marche en Kotlin !
- _Moi_ : [commit](https://github.com/jmfayard/from-java-to-kotlin/pull/1/commits/4b297b8ebcc2153768d3f1e3c78842b113c89f8b)

```diff
diff --git a/pom.xml b/pom.xml
     <dependencies>
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>jsr305</artifactId>
+            <version>3.0.2</version>
+        </dependency>

+++ b/src/main/java/pin/package-info.java
@@ -0,0 +1,4 @@
+@ParametersAreNonnullByDefault
+package pin;
+
+import javax.annotation.ParametersAreNonnullByDefault;
```

## PinGuesser : Convert Java File to Kotlin File

- _Pierre_ : J'imagine que maintenant je peux ouvrir `PinGuesser.java` et relancer l'action  `Convert Java File to Kotlin File` ?
- _Moi_ : Correct
- _Pierre_ : Apparemment.... ça a marché ? En tout cas il y a un fichier `PinGuesser.kt`.
- _Moi_ : Comment peux-tu t'assurer que ça a vraiment marché ?
- _Sarah_ : Tu devrais lancer les tests unitaires.
- _Pierre_ : Ah oui...

![https://user-images.githubusercontent.com/459464/117936889-aaff5280-b305-11eb-9c84-be7205e9673c.png](https://user-images.githubusercontent.com/459464/117936889-aaff5280-b305-11eb-9c84-be7205e9673c.png)

- _Pierre_ : Tout est au vert ! C'est dingue, j'ai écrit mon premier code en Kotlin, et il est bug-free du premier coup.
- _Sarah_ : Bravo !
- _Pierre_ : Il reste les tests. On doit les convertir aussi, non ?
- _Moi_ : Pas forcément, ça marche aussi comme ça. Java et Kotlin peuvent co-exister pacifiquement dans le même repository grâce à leur interopérabilité.
- _Sarah_ : Ok, mais ça a l'air fun et je veux moi aussi essayer !
- _Pierre_ : Je te donne le clavier, juste après ce [commit](https://github.com/jmfayard/from-java-to-kotlin/pull/1/commits/08f5f3d1d5f84a8746a671e3828c18d3d703ae06).

## PinGuesserTest : Convert Java File to Kotlin File - et corrections manuelles

- _Sarah_ : Donc j'ouvre `PinGuesserTest.java` et j'exécute l'action... Comment s'appelle t'elle ?
- _Pierre_ : `Convert Java File to Kotlin File`
- _Sarah_ : C'est parti !
- _Sarah_ : J'ai maintenant un fichier `PinGuesserTest.kt` . Il contient des erreurs ceci dit...

![https://user-images.githubusercontent.com/459464/117937632-80fa6000-b306-11eb-931b-7642e4aac07a.png](https://user-images.githubusercontent.com/459464/117937632-80fa6000-b306-11eb-931b-7642e4aac07a.png)

- _Pierre_ : À ta place j'appliquerais la suggestion d'optimiser les imports.
- _Sarah_ : Ok.
- _Sarah_ : Ça a marché.
- _Moi_ : Comme vous voyez le convertisseur n'est pas parfait. Mais je trouve que c'est un formidable outil d'apprentissage car il vous permet de prendre quelque-chose que vous connaissez déjà - Java - et de le convertir en ce que vous voulez apprendre. 
- _Sarah_ : Je lance les tests unitaires par acquis de conscience.
- _Sarah_ : Ouh j'ai des erreurs bizarres dans jUnit.

![https://user-images.githubusercontent.com/459464/118160523-b3977c00-b41e-11eb-8151-a4dea10aa9e2.png](https://user-images.githubusercontent.com/459464/118160523-b3977c00-b41e-11eb-8151-a4dea10aa9e2.png)

- _Moi_ : Je crois que je comprends le message d'erreur : là où Java a des méthodes **static**, Kotlin utilise des méthodes définies dans le [companion object { ... }](https://kotlinlang.org/docs/object-declarations.html#companion-objects) de la classe. En général c'est presque la même chose, mais là jUnit veut vraiment avoir à faire à des méthodes statiques, ce qui peut se corriger avec une annotation :

```diff

-        fun testSingleDigitParameters(): Stream<ArguMents> {
+        @JvmStatic fun testSingleDigitParameters(): Stream<Arguments> {
             return Stream.of(
                 Arguments.of("1", java.util.Set.of("1", "2", "4")),
                 Arguments.of("2", java.util.Set.of("1", "2", "3", "5")),
@@ -61,7 +58,7 @@ internal class PinGuesserTest {
             )
         }
 
-        fun invalidParams(): Stream<Arguments> {
+        @JvmStatic  fun invalidParams(): Stream<Arguments> {
             return Stream.of(
                 Arguments.of("   "),
                 Arguments.of("A"),

```

- _Sarah_ : Les tests sont au vert !
- _Sarah_ : Comme promis, le projet est maintenant 100% en Kotlin
- _Sarah_ : [commit](https://github.com/jmfayard/from-java-to-kotlin/pull/1/commits/f4d60faa3949f21064edda02f69503d57e37941b)

## Utiliser la librairie Kotlin standard

- _Pierre_ : C'est quoi la prochaine étape ?
- _Moi_ : Il est possible de créer `List`, `Set` et `Map` comme on le fait traditionnellement en Java. Mais la librairie Kotlin standard contient plein de fonctions utilitaires qui résolvent élégamment des petits problèmes courants. Je vous montre ça : 

![https://user-images.githubusercontent.com/459464/118299183-b6f33c00-b4e0-11eb-9458-c8322d65cae9.png](https://user-images.githubusercontent.com/459464/118299183-b6f33c00-b4e0-11eb-9458-c8322d65cae9.png)

- _Moi_ :Je préfère comme ça. Est-ce que les tests sont toujours au vert?
- _Moi_ : Oui ils le sont, donc [commit](https://github.com/jmfayard/from-java-to-kotlin/pull/1/commits/1a5c8dbe3415be22713b3f765f4050515bb91b72).

## Remplacer l'API Streams par la librairie Kotlin standard

- _Moi_ : Une autre chose que contient la librairie Kotlin standard sont les fonctions `.map()`, `.filter()`, `.flatmap()` - et bien d'autres encore - qu'on retrouve dans les langages fonctionnels.
- _Sarah_ : Un peut comme l'API stream() que nous avons utilisé en Java ?
- _Moi_ : C'est ça, mais plus performant dans son implémentation et moins verbeux :

```diff
-    fun combineSolutions(pins1: Set<String>, pins2: Set<String>): Set<String> {
-        return pins1.stream()
-            .flatMap { pin1: String ->
-                pins2
-                    .stream()
-                    .map { pin2: String -> pin1 + pin2 }
-            .collect(Collectors.toSet())
-    }

+    fun combineSolutions(pins1: Set<String>, pins2: Set<String>): Set<String> =
+        pins1.flatMap { pin1 ->
+            pins2.map { pin2 ->
+                "$pin1$pin2"
+             }
+        }.toSet()
```

- _Sarah_ : Les tests passent toujours.
- _Sarah_ : [commit](https://github.com/jmfayard/from-java-to-kotlin/pull/1/commits/82070f0b8e9a39c030da723ddb00f1510efb810c).

## Make val, not var

- _Moi_ : Dans le code Kotlin idiomatique, on essaye d'utiliser le plus possible les `val property` au lieu de `var property`.
- _Pierre_ : Quelle est la différence ?
- _Moi_ : `val property` est read-only, il n'a pas de setter, c'est comme un `final field` en Java
- _Pierre_ : Je vois. Donc je remplace juste les `var` pas des `val` ?
- _Moi_ : Oui c'est ça.
- _Pierre_ : Facile.
- _Pierre_ : [commit](https://github.com/jmfayard/from-java-to-kotlin/pull/1/commits/33b089cad9c90996f2800a0657c0f38d57ba992b).

## Fail fast

- _Sarah_ : Est-ce qu'il y a un moyen idiomatique en Kotlin de vérifier les paramètres ?
- _Sarah_ : On s'attend à ce que le PIN soit quelque-chose comme `7294`, c'est à dire qu'il ne contiennent que des chiffres.
- _Moi_ : Oui, tu peux utiliser `require(condition) { "Message d'erreur" }` .
- _Sarah_ : Cela donnerait quoi ici ?

```kotlin
fun getPINs(observedPin: String): Set<String> {
    require(observedPin.all { it in '0'..'9' }) { "PIN $observedPin is invalid" }
    // rest goes here
}
```

- _Sarah_ : Merci !
- _Sarah_ : [commit](https://github.com/jmfayard/from-java-to-kotlin/pull/1/commits/2424c592495db6f444e30708b88d9b8e2d26cd5e).

## Passer à un style plus fonctionnel

- _Sarah_ : Quelle est la prochaine étape ?
- _Moi_ : Je voudrais libérer les fonctions.
- _Pierre_ : C'est à dire ?
- _Moi_ : Regardez, nous avons cette classe `PinGuesser` . Mais qu'est-ce qu'elle fait exactement ? Elle ne fait rien d'autre qu'être un bête namespace.
- _Moi_ : Cette classe un **nom** qui nous empêche d'accéder directement aux **verbes** - les fonctions - qui font tout le travail.
- _Moi_ : C'est ce que montre un de mes essais favoris sur la programmation : ["Execution in the kingdom of nouns" de Steve Yegge.](http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html)
- _Sarah_ : Je connais cette diatribe, elle est hilarante !
- _Sarah_ : Alors comment libère-t'on les verbes / fonctions ?
- _Moi_ : Nous supprimons la classe et transformons ses méthodes en fonctions top-level :

```kotlin
diff --git a/src/main/java/pin/PinGuesser.kt b/src/main/java/pin/PinGuesser.kt
index 17a20b3..38e457c 100644
--- a/src/main/java/pin/PinGuesser.kt
+++ b/src/main/java/pin/PinGuesser.kt
@@ -1,9 +1,5 @@
 package pin
 
-import java.util.stream.Collectors
-
-class PinGuesser {
-    companion object {
         val mapPins = mapOf(
             "1" to setOf("1", "2", "4"),
             "2" to setOf("1", "2", "3", "5"),
@@ -16,7 +12,6 @@ class PinGuesser {
             "9" to setOf("6", "8", "9"),
             "0" to setOf("0", "8"),
         )
-    }
 
     fun getPINs(observedPin: String): Set<String> {
         for (c in observedPin.toCharArray()) {
@@ -38,5 +33,4 @@ class PinGuesser {
             pins2.map { pin2 ->
                 "$pin1$pin2"
             }
-        }.toSet()
-}


--- a/src/test/java/PinGuesserTest.kt
+++ b/src/test/java/PinGuesserTest.kt
class PinGuesserTest {
-    val pinGuesser = PinGuesser()
 
     @ParaMoiterizedTest
     @MoithodSource("testSingleDigitParaMoiters")
     fun testSingleDigit(observedPin: String?, expected: Set<String?>?) {
-        val actual = pinGuesser.getPINs(observedPin!!)
+        val actual = getPINs(observedPin!!)
         Assertions.assertEquals(expected, actual)
     }
```

- _Moi_ : [commit](https://github.com/jmfayard/from-java-to-kotlin/commit/76273f16bd754477c5a558c003d8434b2cb28c98).

## List.fold()

- _Pierre_ : Est-ce qu'on peut prendre un peu de recul là ? À quoi cela sert-il de rendre le code plus "beau", plus idiomatique ? Au final, nos clients s'en foutent.
- _Moi_ : Et bien je ne sais pas vous, mais moi cela m'arrive fréquemment de ne pas vraiment comprendre le code sur lequel je suis censé travailler. Dans ce cas là, je m'investis pour simplifier le code, et à un moment donné le code tient dans ma tête et la solution devient évidente.
- _Pierre_ : Et qu'est-ce qui est devenu évident là par exemple ?
- _Moi_ : Et bien maintenant que nous avons un code Kotlin idiomatique clean, je me rends compte que la solution du Kata peut s'exprimer par une simple construction fonctionnelle : **List.fold()**.
- _Sarah_ : Show me the code
- _Moi_ : [commit](https://github.com/jmfayard/from-java-to-kotlin/pull/1/commits/025fab52f042a3b077204594f8dd2237b8659304).

```kotlin
fun getPINs(observedPin: String): Set<String> {
    require(observedPin.all { it in mapPins }) { "PIN $observedPin is invalid" }

    return  observedPin.fold(initial = setOf("")) { acc: Set<String>, c: Char ->
        val pinsForChar: Set<String> = mapPins[c]!!
        combineSolutions(acc, pinsForChar)
    }
}

fun combineSolutions(pins1: Set<String>, pins2: Set<String>): Set<String> =
    pins1.flatMap { pin1 ->
        pins2.map { pin2 ->
            "$pin1$pin2"
        }
    }.toSet()
```

## La suite ?

J'espère que vous avez aimé cet article.

Si vous voulez me contacter, vous trouverez mon mail à l'adresse  https://jmfayard.dev/

Le code est disponible à l'adresse https://github.com/jmfayard/from-java-to-kotlin 

Vous devez démarrer dans la branche `java` et comparer avec ce que contient la branche `kotlin`. [**Voir cette pull-request**](https://github.com/jmfayard/from-java-to-kotlin/pull/1)

Si vous voulez apprendre Kotlin, je vous réfère à mon article :

{% link https://dev.to/zenika/comment-apprendre-kotlin-ide-et-navigateur-livres-et-tutorials-debutants-et-dev-java-2nda %}