Par Sébastien Laoût, Ubik Ingénierie
En développement, l'une des sources de bugs est un schéma d'initialisation complexe pour un objet, ou avec trop de règles implicites (tels ou tels setters doivent ou non être appelés pour tel geste métier).
Nous allons voir comment l'immutabilité permet de transformer ces problèmes en erreurs de compilation.
L'initialisation d'un objet se fera en une seule étape atomique par son constructeur, sans risque d'oublier des appels de setters important pour un geste métier en particulier, et sans risque d'interférer avec un autre objet ou thread.
Nous allons aussi surtout voir comment ce simple mécanisme implique la mise en œuvre de diverses autres techniques pour produire un code clean, expressif, et donc une plus faible surface d'attaque pour les bugs.
La présentation est accompagnée de deux exercices de mise en pratique ("kata", dans la terminologie Software Craftmanship) : un expliqué dans la présentation (mais que vous pouvez faire par vous-même à mi-chemin de la présentation), et un autre pour approfondir un cas particulier qui peut rebuter lorsqu'on commence à transformer son code pour utiliser l'immutabilité.
▶ Visionner les diapositives ou regarder la vidéo :
Exercez-vous avec ce premier kata.
Ne vous spoilez pas !
Visualisez uniquement les 28 premiers slides de la présentation pour vous impregnier des avantages de Value Objects immutables.
Une présentation de l'application y est aussi disponible de la slide 26 à la slide 28.
Voici les étapes de la transformation à effectuer :
Le kata est disponible ici :
Code Java de l'exercie 1 sur GitHub (le repository Git contient aussi le second exercice)
Le kata est directement présent sur la branche "main".
En voici les principales classes :
Spoiler : la solution est présente sur la branche "java/exercise1/solution".
Une solution qui va plus loin est présente sur la branche "java/exercise1/solution-bonus".
Cet exercice permet de découvrir par soi-même une réponse à une question récurante concernant l'immutabilité suite à la présentation.
Il arrive régulièrement de devoir initialiser une hiérarchie d'objets.
On crée un objet parent, puis petit à petit, on set() ses objets enfants.
Ou alors on ajoute des enfants à une liste, qui eux-mêmes contiennent d'autres objets, etc.
Comment faire dans le cadre de l'immutabilité ?
En effet, on ne peut pas avoir ces états transitoires non-initialisés : nulls ou listes vides.
Il va falloir inverser sa façon de programmer.
On va initialiser en premier les petits objets les plus bas dans la hiérarchie, puis petit à petit les agréger pour initialiser les objets de plus en plus haut dans la hiérarchie.
Pour mettre en pratique la remarque ci-dessus, je vous propose l'exercice suivant :
https://github.com/slaout/immutability-super-power-kata/tree/main/exercise2/java/src
On a en entrée les lignes d'un fichier CSV qu'on a parsé dans une liste d'objets à plat.
Les lignes sont "dénormalisées" : les colonnes des objets parent ont des données en doublon : une fois pour chaque objet enfant.
Il s'agira de regrouper plusieurs lignes CSV pour créer les objets hiérarchiques.
Ça sera plus clair avec ce test unitaire qui montre les objets en entrée et en sortie :
https://github.com/slaout/immutability-super-power-kata/blob/main/exercise2/java/src/test/java/com/github/slaout/immutability/exercise2/usecase/FlatOrderImportUseCaseTest.java
Voici la classe des lignes CSV plates en entrée :
https://github.com/slaout/immutability-super-power-kata/blob/main/exercise2/java/src/main/java/com/github/slaout/immutability/exercise2/dto/OrderCsvRow.java
Voici les objets hiérarchiques en sortie, à transformer en @Value-Objects immutables :
La classe Order contient des OrderLine et cette dernière contient des Option :
https://github.com/slaout/immutability-super-power-kata/tree/main/exercise2/java/src/main/java/com/github/slaout/immutability/exercise2/domain
Et voici le code à refactorer :
https://github.com/slaout/immutability-super-power-kata/blob/main/exercise2/java/src/main/java/com/github/slaout/immutability/exercise2/usecase/FlatOrderImportUseCase.java
À vos claviers :-)
N'hésitez pas à me proposer vos solutions et remarques.
TODO : schéma !
Le but de l'exercice est de transformer les cinq classes du package domain en Value Objects immutables, et de refactorer la classe UseCase afin d'initialiser les objets.
Le kata est disponible ici :
Code Java de l'exercie 2 sur GitHub (même repository Git que le premier exercice)
Le kata est directement présent sur la branche "main".
Voici la classe à refactorer et son test unitaire associé :
Voici la classe de l'objet passé en paramètre de la méthode à refactorer :
Voici les classes de la hiérarchie d'objets retournés par la méthode à refactorer :
Spoiler : la solution est présente sur la branche "java/exercise2/solution".
Si vous n'aurez pas le temps de le faire, ou si vous voulez juste voir à quoi ça peut ressembler, je vous donne une solution :
ATTENTION : SPOILER :
https://github.com/slaout/immutability-super-power-kata/blob/java/exercise2/solution/exercise2/java/src/main/java/com/github/slaout/immutability/exercise2/usecase/FlatOrderImportUseCase.java
J'ai pris soin d'appliquer les principes du Clean Code pour comprendre facilement la solution :
La solution est ici à base de streams, sous forme de programmation fonctionnelle : aucun objet n'est muté et les fonctions sont "pures".
Mais c'est une programmation fonctionnelle soft / pragmatique : on n'a pas de notions mathématiques absconses telles que des monades, monoids, functors...
On peut bien sûr partir d'une solution à base de boucles for() imbriquées et petit à petit extraire des bouts de code en fonctions à base de streams.
On se rend compte qu'avec un code à base de streams, on démarre la lecture du code de l'objet le plus gros vers ceux les plus profonds, comme le code non-refactoré.
Même si, lors de l'exécution, ce sont bien les petits objets qui sont créés en premier.
Sébastien est un développeur full-stack avec une spécialisation sur le backend Java, principalement dans l'e-commerce, depuis 14 ans.
Touche à tout, il conçoit et développe des applications Spring, des clients VueJS, et même un jeu Android (et il a fait du PHP, mais mieux vaut ne pas l'ébruiter ;-) ).
Ayant à cœur la qualité du code et de ses livrables (architecture technique, clean code et expérience utilisateur), il a une très bonne expérience dans l'automatisation des tests Selenium, Cucumber et Postman.
Présentation effectuée le 26 avril 2022 chez Ubik Ingénierie.
Voir les commentaires présentateur des diapositives concernées.
La présentation est fournie sous licence Creative Commons - Attribution - Partage dans les Mêmes Conditions 4.0 International (CC BY-SA 4.0)