Blog Arolla

On refait le patch : l’outillage Lombok

L’outillage Lombok est présent dans de nombreux projets Java, en effet plus d’un million de personnes se connectent sur le site de Lombok chaque année. Pourtant, le choix de l’utiliser ou non suscite de nombreux débats entre développeurs, mais qui la plupart du temps ne dépassent pas le cadre du subjectif (“j’aime” ou “j’aime pas”). Nous avons consulté des développeurs Java chevronnés pour construire un argumentaire objectif des atouts et faiblesses de la bibliothèque fondée sur nos expériences, anecdotes et traumatismes divers.

Un petit rappel avant tout, qu’est-ce que Lombok ?

Lombok est une bibliothèque open-source Java qui a fait ses débuts en 2009. 10 ans après sa publication, Roel Spilker et Reinier Zwitserloot rappellent l’origine de Lombok en quelques mots.

« If there’s too much code. And it don’t look good. Who you gonna call? Boilerplate Busters! » (Ref 1)

Le but de la bibliothèque est d’éliminer le plus de code qualifié de boilerplate, c’est-à-dire du code verbeux mais offrant peu de valeur ajoutée et ayant tendance à générer beaucoup de bruit dans le code des classes Java. Les éléments suivants sont particulièrement visés : accesseurs (getters et setters), constructeurs, égalité entre deux objets (méthode equals()), calcul du hashCode et rendu textuel (méthode toString()).

L’utilisation habile d’annotations permet de se passer de ces lignes de code superflues. Le procédé n’est toutefois pas magique, parce que bien que ces lignes ne soient plus visibles, elles continuent à exister. L’astuce consiste à les rendre absentes dans les sources, mais présentes dans le code compilé. Ainsi, avant la phase de compilation du code source, une phase de build est ajoutée par l’outillage Lombok, qui parcourt les annotations dédiées pour générer le bytecode correspondant.

Un gain de productivité et de lisibilité

Lombok s’occupe d’écrire pour nous une bonne partie du code rébarbatif. Ainsi la classe Person, traditionnellement implémentée de la manière suivante :

public class Personne {
   private String fullname;
   private String birthday;
   private List contacts;

public Personne() {
   }

public String getFullname() {
       return fullname;
   }

public void setFullname(String fullname) {
       this.fullname = fullname;
   }

public String getBirthday() {
       return birthday;
   }

public void setBirthday(String birthday) {
       this.birthday = birthday;
   }

public List getContacts() {
       return contacts;
   }

public void setContacts(List contacts) {
       this.contacts = contacts;
   }

@Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
       Personne personne = (Personne) o;
       return Objects.equals(fullname, personne.fullname) &&
               Objects.equals(birthday, personne.birthday) &&
               Objects.equals(contacts, personne.contacts);
   }

@Override
   public int hashCode() {
       return Objects.hash(fullname, birthday, contacts);
   }

@Override
   public String toString() {
       return "Personne{" +
               "fullname='" + fullname + '\'' +
               ", birthday='" + birthday + '\'' +
               ", contacts=" + contacts +
               '}';
   }
}

Ce code peut, avec Lombok, se simplifier ainsi :
@Data
public class Personne {
   private String fullname;
   private String birthday;
   private List contacts;
}

Le gain en concision est indubitable, et surtout, le code n’a pas perdu en clarté. Bien au contraire, l’information recherchée est plus facilement accessible, et cela nous permet de nous concentrer sur l’essentiel. Côté écriture du code, l’effort à fournir se limite à utiliser les bonnes annotations.

Un gain énorme à la maintenance

L’argument suivant nous a été opposé : le gain en productivité était surtout vrai il y a 10 ans, mais actuellement les IDE (et notamment IntelliJ) sont capables de générer automatiquement tout ce code, et même de le masquer si besoin.
Cet argument est valable, notamment lors de l’écriture initiale de la classe. Par la suite, lorsqu’on va vouloir ajouter des attributs, ou refactorer en déplaçant des attributs vers d’autres classes ou en découpant la classe, les choses vont se compliquer, notamment pour conserver la cohérence des méthodes equals(), hashCode(), et toString(). La raison est que ces méthodes, contrairement aux accesseurs, mettent en œuvre l’ensemble (ou au moins plusieurs) des attributs de la classe. Chaque modification de l’ensemble des attributs d’une classe entraîne ainsi un effort de maintenance pour ces méthodes.
Il faut en effet les régénérer, est-ce si compliqué ? L’IDE sait le gérer, mais seulement à la demande. Le risque réside donc surtout dans la possibilité d’oublier de les régénérer (et donc par cet oubli d’introduire des bugs).

Lombok, en régénérant le code boilerplate à chaque compilation, fournit une aide importante pour éliminer cette charge mentale. Là où Lombok est très fort, c’est que peu importe l’évolution des attributs d’une classe, le code boilerplate sera toujours cohérent.

De manière analogue, d’autres annotations permettent d’avoir une classe immutable facilement, ou encore de gérer les builders/constructeurs automatiquement.

Montée en compétence sur Lombok

Lombok rend plein de services, mais en le faisant sous le manteau, au risque de paraître magique pour certains. Pire encore, d’autres pourraient totalement ignorer ce que fait Lombok (par exemple un membre de l’équipe débutant en Java dont le premier projet utilise Lombok).

Pour que l’utilisation de Lombok soit efficace et n’entraîne pas d’effet pervers, il est important que les coéquipiers soient alignés et au même niveau de connaissance, à la fois sur l’outil Lombok que sur les bonnes pratiques d’utilisation sur le projet.
Cela passe par une bonne connaissance du sens des annotations et du code implicite qu’elle vont produire. Par exemple, il est important de savoir que l’annotation @Value va appliquer les modificateurs private et final sur les attributs de la classe, de façon à garantir au mieux leur immutabilité.
Il reste important de préciser que s’approprier Lombok ne nécessite pas un effort surhumain : effectivement, Lombok ne regroupe qu’une dizaine d’annotations et on en utilise en général qu’une poignée. Mais cet effort, bien que minime, reste nécessaire pour éviter les mauvaises surprises.

Impacts de Lombok sur le design

Comme mentionné ci-dessus, l’annotation @Value peut aider à rendre une classe immutable et aide donc à l’écriture des Value Objects. L’annotation @Data est parfaite pour les classes DTO (classes sans comportement servant à véhiculer de la donnée structurée) et pour les Value Objects, qui sont immutables et définis par la valeur de leurs attributs et non plus la notion d’instance d’objet.

Il est important de bien choisir les annotations Lombok en fonction de la situation. Par exemple, @Data est parfois utilisée comme l’annotation à tout faire (elle équivaut à la somme de @Getter, @Setter, @ToString, @EqualsAndHashCode and @RequiredArgsConstructor). Mais au final avons-nous besoin de tout ça ? La question est importante, parce qu’une fois le choix effectué, il est plus difficile de revenir en arrière (“s’ils ont mis @Data, ce doit être pour une bonne raison, non ?”). L’usage des annotations doit se faire avec parcimonie, pour répondre à un besoin, et pas l’inverse (ne pas essayer de couvrir par défaut tous les besoins qui pourraient un jour se présenter) (ref 2).

Par ailleurs, un des critères qui nous permet de savoir si une classe est bien découpée est son nombre d’attributs. Cela était particulièrement visible avec le nombre de lignes que la classe occupait mais ce n’est plus forcément le cas avec l’utilisation de Lombok. Même si la classe ne fait plus que quelques lignes, il est important de ne pas sous-estimer sa complexité réelle.

Impacts de Lombok sur l’écosystème du projet

Lombok est en apparence simple à installer, il suffit d’ajouter la dépendance au projet.

Les projets Java utilisent d’autres bibliothèques qui produisent du code invisible tel que Jax-RS pour les endpoints de l’API pour éviter de la maintenance ou bien MapStruct. Le risque de conflit lorsque ces dépendances génèrent le bytecode et interagissent entre elles est difficile à prévoir, surtout lors de leurs montées de version.

Par ailleurs, l’intégration de Lombok dans le projet crée un couplage supplémentaire. Comme toute brique tierce, la question du support à long terme reste légitime. Par le passé, il est même arrivé que Lombok freine la montée de version de Java : ainsi, à la sortie de Java 9, Lombok ne supportait pas encore totalement le nouveau JDK, et la compilation des projets avec Lombok échouait (voir changelog de Lombok).
De la même façon, certaines incompatibilités avec l’IDE étaient parfois à noter, de même que la nécessité d’installer un plugin spécifique lui permettant d’interpréter les annotations Lombok.

Les angles morts du code implicite

Une fois familiarisé avec Lombok, on en vient rapidement à oublier sa présence. Lombok sait tellement se faire tout petit qu’en cas de problème, on aura du mal à penser qu’il peut être fautif.
Dans les anecdotes des développeurs interrogés, il n’était pas rare d’avoir perdu plusieurs jours pour comprendre la cause d’un problème, en particulier parce que l’utilisation de Lombok en était l’origine.

Lombok, c’est du code qui fait partie de l’application, mais qui n’est plus visible directement. Les impacts sont les suivants :
lors du debug, le code généré n’est pas directement sous nos yeux ; de plus il sera difficile voire impossible de poser un point d’arrêt à l’intérieur du code généré par Lombok
des limitations sont à prévoir dans la navigation du code, la documentation et la recherche d’une méthode

Malgré cela, les bugs liés à Lombok sont très rarement imputables à la bibliothèque elle-même, mais proviennent plutôt de mauvaises utilisations ou d’une mauvaise compréhension de son fonctionnement. Quelle que soit la cause réelle, de tels dysfonctionnements restent difficiles à qualifier.

Ironie du sort, les auteurs de Lombok ont eux-mêmes été confrontés à ce genre de difficulté. Ainsi, dans leur vidéo de feedback à l’occasion des 10 ans de l’outil, ils partagent leur difficulté à maintenir le projet open source en prenant pour exemple la correction d’un bug remonté par un utilisateur. L’origine du problème était une race condition dans leur code liée à une variable globale. La correction leur a pris 6 mois, en particulier à cause de la difficulté de reproduction du bug.

Grand pouvoir, grandes responsabilités

La part de l’implicite dans un projet logiciel n’est pas l’exclusivité de Lombok. Certains langages (ou versions de langages), frameworks ou autres outils, produisent beaucoup d’implicite à travers leur déclarativité. Dans l'éco-système Java, les exemples les plus connus sont Spring ou Hibernate. Les services offerts sont immenses, offrant ainsi aux développeurs un immense pouvoir mais leur fonctionnement paraît magique voire obscur pour certains.
Certains font l’effort de monter en compétence pour maîtriser leur plate-forme, d’autres restent dans une situation où ils ne comprennent pas ce qu’ils font. Avec des conséquences dramatiques en production, telles que des failles de sécurité ou des performances désastreuses.

Ce n’est pas parce que ces outils font une grande partie du travail à la place du développeur que ce dernier doit oublier les fondamentaux et ignorer ce qu’il se passe sous le capot. Ainsi, les choix et l’utilisation doivent être faits en connaissance. Comme le disait Ben Parker (ref 3), “un grand pouvoir doit s’accompagner de grandes responsabilités”.

En l’occurrence, la responsabilité du développeur est de fournir les efforts nécessaires pour connaître et maîtriser ses outils.

Et ailleurs, c’est comment ?

En Kotlin, les getters/setters sont implicites en fonction du niveau de visibilité du champ et il est possible de qualifier sa classe de data class ou de case class.
En langage Go, l’accessibilité de l’attribut se définit par sa casse (par exemple, une majuscule pour rendre l’attribut publique).
En Groovy, les accesseurs existent par défaut sans devoir les définir. D’ailleurs Groovy supporte implicitement la plupart des fonctionnalités que Lombok offre à Java.
Ceux qui développent dans ces langages ne semblent pas mécontents de ces facilités, pourtant similaires à ce qu’apporte Lombok. Ces comportements sont natifs au langage et font donc partie des connaissances communes à tous les développeurs. En outre, ces fonctionnalités sont présentes gratuitement, sans ajouter de couplage supplémentaire.

Java semble d’ailleurs suivre cette direction en introduisant, en version 14, les records. Ceux-ci permettent de créer des classes immutables à partir d’une définition simplifiée, rendant inutile l’annotation @Value. En faisant preuve d’un peu d’optimisme, on peut imaginer que les prochaines versions de Java rendront Lombok totalement caduque ...

Finalement, Lombok, bien ou pas bien ?

A la suite de cet argumentaire, il peut vous être difficile de choisir d’utiliser (ou conserver) Lombok ou non. Il est indéniable que cet outil présente de nombreuses qualités.
Certains avis négatifs sont liés à un manque de maîtrise du fonctionnement et de mauvaises expériences autour de Lombok. Dans ces situations, nous pensons qu’il est dommage de se priver de sa puissance alors qu’il est probablement moins coûteux d’investir sur la compréhension de l’outil et des bonnes pratiques associées.
Néanmoins, pour des projets reposant sur des éco-systèmes techniques complexes, il peut être préférable de restreindre l’usage de Lombok à certaines classes (DTO, Value Objects) afin de limiter les mauvaises surprises.
Qu’on choisisse d’utiliser Lombok ou non, la décision devrait se faire collégialement et de façon éclairée.

Un grand merci à Antoine Alberti, Arnaud Courtès, Quentin Dorme, Laurent Gautho-Lapeyre, Julien Genty, Kevin Hantzen et Christian Spérandio, tous développeurs chez Arolla, pour les discussions endiablées qui ont mené à l’élaboration de cet article.

Références :

  1. Les auteurs de Lombok se comparent, en cherchant à éliminer le code boilerplate, jugé inutile, aux célèbres chasseurs de fantômes des films Ghostbusters. Voir début de la vidéo : https://devoxx.be/schedule/speaker-details/?id=25051
  1. D’une certaine manière, on rejoint ici le principe YAGNI (You Ain’t Gonna,Need It) cher aux artisans développeurs.

  2. L’oncle regretté de Peter Parker, aka Spiderman

Plus de publications
Plus de publications

Comments are closed.