Aujourd'hui j'ai relu un cours de @Kysofer qu'il prépare sur la bonne utilisation des monades en programmation fonctionnelle et ça m'a donné envie de le résumer en un cas d'école clair et simple.
Pour la petite histoire et depuis Java 8, je vois apparaître des Optionals
partout mais 99% des développeurs n'ont pas compris comment s'en servir (c'est pour cette raison que je dis souvent que la programmation fonctionnelle est un cancer métastasé, car une fois ces mauvaises pratiques installées, le code devient alors incurable). #Sadness
Bref, c'est partie pour une explication claire et courte... Notre cas d'école sera le suivant :
Une
Personne
possède uneDate de Naissance
, cette date contient elle-même uneAnnée de Naissance
qui contient à son tour une valeur (logée dans un Integer).L'objectif est d'imprimer l'année de naissance si elle existe sans jamais écrire le moindre 'if' pour l'exécution ci-après déclarée dans la méthode
main()
.
La méthode main()
:
class Main {
public static void main(String[] args) {
optionalPrintln(Optional.ofNullable(new Person(new BirthDay(new YearOfBirth(2000)))));
optionalPrintln(Optional.ofNullable(new Person(new BirthDay(null))));
optionalPrintln(Optional.ofNullable(new Person(null)));
}
}
La sortie attendue dans la console doit être celle-ci
2000
Étape 1 - Les classes/structures de données Person, BirthDay et YearOfBirth
class Person {
private final BirthDay birthDay;
public Person(BirthDay birthDay) {
this.birthDay = birthDay;
}
public Optional<BirthDay> getBirthDay() {
return Optional.ofNullable(birthDay);
}
}
class BirthDay {
private final YearOfBirth year;
public BirthDay(YearOfBirth year) {
this.year = year;
}
public Optional<YearOfBirth> getYearOfBirth() {
return Optional.ofNullable(year);
}
}
class YearOfBirth {
private final int year;
public YearOfBirth(int year) {
this.year = year;
}
public Optional<Integer> getValue() {
return Optional.of(year);
}
}
Étape 2 - Ce qu'il ne faut pas faire
Les 9-10ième des développeurs Java que je côtoie écriront ce genre de code pour la optionalPrintln(Optional<Person> option)
:
class Main {
// ...
public static void optionalPrintln(Optional<Person> option) {
if (option.isPresent()) {
Person person = option.get();
Optional<BirthDay> birthDay = person.getBirthDay();
if (birthDay.isPresent()) {
Optional<YearOfBirth> yearOfBirth = birthDay.get().getYearOfBirth();
if (yearOfBirth.isPresent()) {
Optional<Integer> year = yearOfBirth.get().getValue();
if (year.isPresent()) {
System.out.println(year.get());
}
}
}
}
}
}
C'est très moche, c'est très compliqué et ça n'est pas fonctionnel du tout ! Pire encore, certains développeurs écriront ceci :
year.ifPresent(it -> System.out.println(it));
à la place de ce dernier 'if' :
if (year.isPresent()) {
System.out.println(year.get());
}
tout en croyant coder en fonctionnel, or ça n'est pas le cas du tout non plus ! Croire que l'on fait bien alors que l'on fait mal, c'est en ce sens que la chose est pire AMHA.
Étape 3 - Ce qu'il faut faire
La programmation fonctionnelle ne cherche pas à représenter des instructions, c'est-à-dire une succession d'actions techniques qui s'enchaînent mais a contrario, elle cherche à représenter un flux de conversion, c'est-à-dire le fait de passer d'un type A à un type B.
L'API permettant une telle prouesse réside dans le map
de la stream API, or cette fonctionnalité n'est pas applicable à un objet composé comme c'est le cas du Optional
puisqu'il s'agit d'un arbre à deux niveaux sur une seule branche.
Dans ce cas, il faut utiliser la méthode flatMap()
de la même API pour supprimer le niveau superflue de l'arbre et accéder directement à la donnée si elle n'est pas null
.
Ce qui donne le code suivant :
public static void optionalPrintln(Optional<Person> option) {
option.flatMap(it -> it.getBirthDay())
.flatMap(it -> it.getYearOfBirth())
.flatMap(it -> it.getValue())
.ifPresent(it -> System.out.println(it));
}
Explication
A chaque invocation de flatMap()
, cette méthode va jouer pour nous un optional.ifPresent(it -> ...)
afin de nous en décharger. Si rien n'est présent, alors l'évaluation du flatMap()
lui succédant ne se fera tout simplement pas car le flux d'instances à convertir d'un type à un autre type sera dépourvu de toute instance à convertir. #Malin
Et c'est comme cela que l'on code en fonctionnel avec les Optionals ! Le véritable but étant de ne jamais s'en servir explicitement.
Remarque :
La programmation fonctionnelle est extrêmement difficile car elle oblige les développeurs à passer du stade "je dis à la machine comment faire" au stade "je décris à la machine quoi transformer et vers quoi d'autre".
Depuis ces 20 dernières années, j'ai toujours constaté que moins de 5% des développeurs étaient capables de penser dans ce paradigme et parmi ce petit pourcentage, à peine la moitié s'en servira dans sa vie professionnelle.
Si vous codez dans un langage à destination d'une JRE (Kotlin, Java, Scala, Groovy, etc) vous avez sûrement entendu parler de Spring Boot. Pour ce qui ne le connaissent pas, c'est LE framework qui est parti de rien (à l'origine c'était seulement Spring Framework), puis qui a grossi tout doucement depuis 15 ans et est devenu aujourd'hui un mastodonte aussi gros, lourd, pénible et lent à démarrer que l'ancien JEE (avec des Websphere et Weblogic, etc).
Mais en réalité est-ce que c'est mal ?
Pas vraiment, Spring Boot pousse à produire du code en couche avec des singletons présents partout à chaque couche. Ce n'est pas comme cela que l'on écrit du code concis, découplé, court et maintenable, mais ça a le mérite de s'écrire vite, d'être simple pour des débutants et de fonctionner quand même (en tout cas au début). Après c'est un enfer à tester en terme d'écriture et de temps d'exécution mais bon, qui teste encore son code en 2021 ? #Sadness
Alors quel est mon problème ?
Mon problème c'est que parmi l'ensemble des développements actuels auxquels je contribue chez mes clients, ceux-ci sont couplés totalement à Spring Boot. Vous montez de version, vos développements risquent de péter, vous souhaitez quitter Spring Boot pour autre chose de plus rapide comme Quarkus, pas possible les libs ont été codées pour Spring Boot, d'ailleurs pour les utiliser aucune lib ne pourra se charger si vous n'avez pas Spring Boot car seul Spring Boot doit être en mesure de les instancier. #Folie
Et c'est ça mon problème, Java avait supprimé la problématique de nettoyage de la mémoire, Spring Framework avait supprimé la problématique de création des instances et Spring Boot supprime aujourd'hui la notion d'architecture (ce qui n'a pas du tout le même impact puisque ça touche à la capacité d'innover et de faire différemment), or un code propre qui soit fonctionnel et objet requiert la création et la destruction permanente d'instances immutables à cycle de vie ultra court (une action puis poubelle), ce qui est l'antithèse même des singletons omniprésents, paramétrés par AOP (ndr. Aspect Oriented Programming) et poussés par Spring...
En synthèse :
Pour toutes ces raisons, Spring Boot va à mon sens à contre courant des meilleures pratiques de développement connues et reconnues à cette heure parce qu'il préserve les façons de coder d'il y a plus de 20 ans et venant de langages ni objets ni fonctionnels comme le C, pire encore les "développeurs Spring Boot" sont tellement à fond dans Spring Boot qu'ils n'arrivent même plus à penser leurs libs comme des éléments qui puissent s'utiliser en dehors de Spring Boot et c'est ce qui me fait dire que Spring Boot est un contre-choix et une fausse-bonne idée.
Bon, la plupart d'entre-vous s'en doutait, je suis militante UPR mais j'ai beau avoir intégré ce que certains appellent "la milice la plus active sur internet", je suis quand même en mesure d'affirmer que leur site est une bouse technique sans nom.
Pourquoi une si vive cette critique ?
C'est simple, sur ma connexion à 1,5 Mo / sec, le site met 47,12 secondes, c'est-à-dire que le site est inaffichable sur mobile alors imaginez dans le métro / rer...
En décortiquant le chargement de la page, la raison parle d'elle-même :
- 13,84 Mo de contenu pour 12,14 Mo de transférés (merci à la compression GZip pour le gain de 10% sachant qu'en général celle-ci fait plutôt gagner 50%).
- 226 requêtes HTTP (oO) !
Bon-sang 226 requêtes, c'est énorme mais le pire n'est pas là. Non le pire c'est que le site ajoute le mode "http no-cache" dans le header, ce qui fait que recharger la page coûte quasiment autant que la première fois !!! Sérieusement, le site de l'UPR étant le second moyen de se concerter entre militants, le principale étant le triplet twitter / facebook / youtube, une lenteur pareille est inacceptable.
Et pour couronner le tout, la délégation de ma région ne répond pas lorsqu'on lui écrit donc bon... #Sadness
Heureusement que le nombre d'adhérents retrouve une bonne croissance ces derniers temps, au moins cela me réconforte.
J'avais prédis l'élection d'un certain Macron face à une certaines Le Pen. #Sadness
Que le prochain qui me dit que le PS ou l'UMP ne représentent pas une mafia s'attende à s'en manger une.
#Sadness :'(
Je pense qu'il est grand temps de voter pour des partis qui n'ont encore jamais été élus comme le Parti Pirate (à défaut du tirage au sort).
L'article est vraiment bien et montre comment la particratie en place truche les élections... #Sadness