Ici à ARCA, on croit à Kotlin comme successeur à Java. Langage propulsé par JetBrains et adoubé par Google, il est pourtant encore trop peu utilisé. Entre idées reçues et peur de l’avenir, beaucoup se refusent à passer le pas. Regardons ensemble pourquoi Kotlin est l’avenir du Java, et comment faire pour prendre ce virage.
# 1. Kotlin, kézako
Pour celles et ceux qui ne le sauraient pas, Kotlin est un langage développé par la compagnie JetBrains (derrière IntelliJ, PyCharm, Webstorm et consorts), qui prône la programmation orientée objet et la programmation fonctionnelle. La première version officielle est déployée en 2011, mais tout change en 2017 lorsque Google annonce que Kotlin est officiellement supporté pour Android, puis en 2019 lorsque la firme de Mountain View annonce que ce langage devient le langage officiel et recommandé pour Android. Autant dire que tous les développeurs Android y sont déjà passés. Cependant, Kotlin n’est pas réservé au développement mobile, et c’est ce que nous allons démontrer.
Pour la petite histoire, Kotlin est aussi une petite île russe (Ко́тлин), d’où l’image de couverture de cet article !
# 2. Idées reçues et contre arguments
• Le Kotlin est un langage fait pour Android
Situation : un recruteur voit « Kotlin » sur un CV, et s’exclame « Parfait nous cherchons un développeur Android ! » (true story). Certes, Google en a fait le langage officiel d’Android, mais c’est aussi beaucoup plus que ça ! Kotlin est extrèmement versatile, et sur le site officiel Android n’est qu’en 4e position de cas d’utilisations. Kotlin est multi-plateforme, du javascript à l’iOS, et se compile en bytecode interprété directement par la JVM. Donc tout ce que Java fait, Kotlin fait. Android inclus, mais aussi bien plus…
• Je n’ai pas les compétences [dans mon équipe] pour faire du Kotlin
Je répète : Kotlin compile vers la JVM ! Donc, si vous savez faire du Java, vous savez faire du Kotlin. Enfin, en partie. Il y a évidemment des subtilités (par exemple, le type vient après la déclaration), mais la logique de classe et les méthodes peuvent être utilisées de la même manière. Nous reviendrons sur la syntaxe un peu plus tard. Certains IDE vous proposerons même une conversation automatique si vous collez un snippet Java dans un fichier Kotlin.
• J’ai déjà un projet en Java, ça couterait trop cher de le réécrire d’un coup
Jamais deux sans trois : Kotlin compile vers la JVM ! Ce qui veut dire qu’après un peu de configuration expliquée dans la documentation officielle, un projet peut mixer des fichiers *.java et *.kt en toute harmonie. Si on revient à l’histoire de migration, ça signifie que l’on peut tester sur une nouvelle feature en Kotlin sans toucher au projet Java, ou migrer les fichiers petits à petits. Une migration (presque) indolore, que demande le peuple ?
# 3. Exemples et introduction à Kotlin
Maintenant que tout cela est dit, qu’est-ce qu’on attend pour écrire du Kotlin ? Assez parlé de pourquoi vous devriez faire la transition. Voyons ensemble comment.
data class et immutabilité
Kotlin nous incite fortement à utiliser les data class
plutôt que les class
pour représenter de la donnée. Pourquoi ? Parce que les data class
sont par défaut immutables, et hashables. Car Kotlin va générer automatiquement les méthodes equals()
, hashCode()
, et toString()
. Voyez plutôt :
On notera l’utilisation de var
: Kotlin possède deux types de déclaration. var
pour variable et val
pour valeur, qui correspond à un attribut final
en Java. Mais si les data class sont immutables, comment les faire évoluer ? Grâce à une méthode générée copy
, qui est en réalité un constructeur dont tous les paramètres sont optionnels et avec les attributs du parent comme valeur par défaut.
let, also, run, apply
Kotlin étant orienté programmation fonctionnelle, il y a toujours un moyen de chaîner nos méthodes. Et même si ce moyen n’existe pas, Kotlin met à disposition 4 méthodes indispensables qui prennent en paramètre une lambda : let
, also
, run
et apply
. Ils sont en réalité assez similaires :
Mais alors, pourquoi avoir fait des méthodes si similaires ? La différence entre la valeur de retour justifie une première duplication, et la différence entre this ou it se justifie pour éviter le shadowing si on imbrique des let
et des run
par exemple, mais au jour le jour les méthodes sont relativement interchangeables. Pour plus d’informations, voir la documentation officielle sur les Scope Functions. En contexte, cela peut donner la chose suivante :
Oulah. Que de choses à dire sur si peu de lignes ! On a déjà vu les data class, et les var
. fun
permet de déclarer une méthode, comme ceci :
fun nom(parametres: Type): Type de retour { /* Code */ }
Ici le type de retour est Unit
, qui est le type void
en Kotlin.
- Ligne 7, on réassigne la valeur de position, c’est possible car position est une variable (ligne 4). En réalité, on réassigne
this.position
car en Kotlin, on peut omettrethis
, et l’écriture des getters et des setters est simplifiée automatiquement. La même ligne en Java donnerait doncthis.setPosition(this.getPosition()...)
et le reste du bloc à l’intérieur du setter. Bien plus verbeux. - Ligne 8, on utilise
also
pour afficher l’état.Position
est donc assigné àit
, que l’on voit utilisé dans la string qui sont toutes destemplates string
par défaut (d’où l’utilisation de$
devantit
pour avoir l’interpolation) - Ligne 9 à 14, on ouvre le bloc
run
.Position
est assigné àthis
, mais n’est utilisé nulle part dans le bloc ?? Le contexte se charge de l’interpolation. Ainsi,this.copy(...)
devientcopy(...)
, etthis.getHorizontal()
devientthis.horizontal
puis seulementhorizontal
. - Enfin, la valeur de retour du bloc
run
est la valeur de retour de la méthodecopy
car c’est la dernière ligne du bloc.
Cet exemple est un peu costaud je le reconnais, mais réécrire la même chose en Java prendrait au moins le double de place, donc le double de temps à lire et à comprendre. Là le code est net, précis, simple.
Gestion des nulls
La gestion de la nullité dans Kotlin est native : plus besoin de tester si votre valeur est nulle partout dans votre code, il suffit de chainer avec un ?.
si la valeur peut être nulle, et ?:
pour expliciter quoi faire dans le cas null
si nécessaire. Un petit exemple pour la route ? Le bloc takeIf
renvoit la valeur de contexte si la condition est vraie, null sinon :
Infix, parlez à votre code !
Bon, celle là c’est un peu pour le fun. Mais Kotlin propose le mot clé infix
, à placer devant la déclaration de méthode de classe, qui ne peut prendre qu’un seul paramètre. Si l’on reprend notre sous-marin, et que la méthode moveTo
devient infix, et que l’on veut donner une série de positions successives à atteindre, voila ce que ça donnera :
Les cas d’utilisations concrets ne sont pas légion, mais si Kotlin peut vous proposer une écriture plus simple à lire il le fera. On ne le répètera jamais assez, mais un code maintenable est avant tout un code lisible sans prise de tête !
Fonctions d’extension, de la bonne façon
Vous êtes vous déjà retrouvé(e)s dans la situation ou le domaine de l’application est bien défini, mais vous avez besoin de faire une opération sur le modèle dans un service en particulier ? En Java, deux solutions : soit il faut modifier le modèle, et faire rentrer du code utilitaire dans un objet auparavant bien pur et cohérent avec le métier ; soit il faut mettre cette fonction utilitaire dans le service qui en a besoin, quitte à dupliquer cette malheureuse fonction dans tous les services nécessaires. Mouais.
Et Kotlin inventa les fonctions d’extensions…
Les fonctions d’extensions sont des fonctions déclarées au besoin sur une classe. Ces fonctions n’ont pas besoin d’être dans une classe, car contrairement à Java Kotlin permet d’écrire du code qui n’est pas Objet. Dans ce cas, ces méthodes seront visibles par tout le paquet, ou seulement par le fichier courant avec le mot clé private
. Reprenons notre exemple de displayDeepBluePosition
, qui ne fait aucun sens dans notre classe Position, mais est nécessaire pour un bout de notre programme. L’extension s’écrira alors :
Ici, le paramètre de displayDeepBluePosition
est toujours une Position, mais passé dans le contexte avec this
. Notez qu’on aurait pu omettre le this
ligne 6, et qu’il a été omis ligne 7. On a aussi omis le type de retour de la fonction, car Kotlin sait inférer le retour automatiquement.
#4 Mise en condition
Bon forcément, des exemples qui paraissent séduisants c’est sympa et c’est facile à écrire, mais qu’est-ce que ça donne sur du vrai code de production ? Dans l’équipe de rédaction, on a relevé le défi. On a pris un service écrit en Java, et on a créé son jumeau Kotlin. L’IDE nous a tout de suite prévenu : c’est le premier fichier Kotlin du projet, que voulez vous en faire ? Il nous a suffit de rajouter 3 lignes dans le pom.xml (1 si vous utilisez gradle) et le projet a accepté notre fichier comme un service valide. Facile !
Bon et donc ? Évidemment, nous n’allons pas vous montrer tout le fichier, car ça serait illisible, mais voici ce que nous pouvons faire (pour le respect du code présenté, les noms de méthodes et de paramètres ont été modifiés).
En partant de cette méthode Java :
On arrive à cette méthode Kotlin :
Oui, je vous l’assure, ce code fait exactement la même chose. En 3 fois moins de lignes ! Et encore, le typage en Long
alourdi le Kotlin, et ce typage vient du count()
de Java. On pourrait s’en passer, mais ça serait changer la signature de la méthode alors on s’est dit que c’était de la triche. Globalement, le fichier passe de 515 lignes (23622 caractères) à 385 lignes (17030 caractères), soit environ 26% moins de code (donc à mon avis encore 285 lignes de trop mais ça c’est un autre débat). Et moins de lignes à coder, c’est moins de lignes à maintenir. Win win !
Pingback: Ktor, un framework du tonnerre | ARCA Computing()