<
Media
>
Article

Groovy, le langage JVM orienté productivité

7 min
01
/
12
/
2022

Intro

"Développeur/euse Java", ça ne serait pas un peu réducteur en fait ?

Ce n'est pas du Java qui tourne sur nos serveurs et nos smartphones.

Et non, c'est bien du bytecode.

On peut le générer avec plusieurs langages :

  • Java (no kidding)
  • Groovy
  • Kotlin
  • Scala
  • Clojure
  • JRuby
  • Jython

Le bytecode généré est inter-compatible quel que soit son langage d'origine.

Cela signifie qu'une application Java peut avoir des classes dans un de ces langages, et elles peuvent toutes s'appeler entre elles.

On retrouve souvent ces langages parallèles dans des outils liés au test, à de la configuration et à l'automatisation :

  • Groovy dans Spock, Jenkins, Gradle, Geb, Soap UI
  • Scala dans Gatling
  • Kotlin dans Gradle

Et on peut parfaitement les utiliser dans du code de production, ce que j'ai fait pendant presque 2 ans par le biais du framework Grails (Groovy On Rails). J'avais trouvé ce langage très efficace en termes de productivité et d'intelligibilité.

Cet article présente les fonctionnalités de Groovy pour coder ses idées à toute vitesse.

Les facettes de Groovy

Voici les caractéristiques et principes qui, d’après moi, permettent à Groovy d’être si efficace :

Facette 1 : Une courbe d’apprentissage plate

La transition Java vers Groovy se fait très facilement.

Pourquoi ?

Une ligne Java compile aussi en Groovy.

Et oui, on peut donc apprendre Groovy itérativement depuis du Java.

De nombreuses simplifications de code sont alors proposées par l'IDE et permettent de découvrir les fonctionnalités du langage.

Facette 2 : Des conventions plutôt que de la configuration

Admettons-le : configurer, c’est lourd. C’est surtout dans le design et l’écriture du code métier qu’on souhaite dépenser notre précieux temps.

C’est probablement pour cette raison que les frameworks Springboot et Micronaut sont si appréciés ; ils appliquent ce concept.

Voici ma sélection de 10 fonctionnalités Groovy qui soutiennent le principe de "convention-over-configuration".

Fonctionnalité 1 : Plus besoin de point virgule

Attendez, je le répète : plus besoin de point virgule !

C’est une étape décisive pour l’obtention d’un code avec une meilleure charge utile.

Fonctionnalité 2 : Littéraux de <span class="css-span">List</span> et de <span class="css-span">Map</span>

Si je devais citer une seule fonctionnalité de Groovy, ce serait celle-ci. Déclarer et initialiser des <span class="css-span">Collection</span> et des <span class="css-span">Map</span> en Java est très verbeux.

Comparez vous-même pour une <span class="css-span">ArrayList</span> (mutable) :

Java :

var utilisateurs = new ArrayList<>() {{
 add(utilisateur1);
 add(utilisateur2);
}};

Je suis sympa, c’est du Java 11, ce qui nous économise le type. Sinon ça aurait été pire.

Groovy :

<pre><code>var utilisateurs = [    
     utilisateur1,    
     utilisateur2,
]</code></pre>

Par convention, <span class="css-span">utilisateurs</span> est une <span class="css-span">ArrayList</span>. Mais on aurait pu avoir un autre type de <span class="css-span">Collection</span>, voire un <span class="css-span">array</span> :

<pre><code>Utilisateur[] utilisateurs = [    
     utilisateur1,    
     utilisateur2,
].toArray()

LinkedList<Utilisateur> utilisateurs = [    
     utilisateur1,    
     utilisateur2,
] as LinkedList

Set<Utilisateur> utilisateurs = [    
     utilisateur1,    
     utilisateur2,
].toSet()

var utilisateurs = [    
     utilisateur1,    
     utilisateur2,
].asImmutable()</code></pre>

Même chose pour les littéraux de <span class="css-span">Map</span>, dont voici la comparaison de syntaxe Java vs Groovy :

En Java (et à grand renfort d’imports statics) :

<pre><code>import static java.util.Map.entry;
import static java.util.Map.ofEntries;

var utilisateurParGroupe = ofEntries(    
     entry(groupe4,utilisateur1),    
     entry(groupe2,utilisateur2)    
     );</code></pre>

Là aussi, on est très content du type dynamique, à partir de Java 11.

Mais en Groovy, c’est bouilli à la plus pure charge utile :

<pre><code>var utilisateurParGroupe = [    
     (groupe4): utilisateur1,    
     (groupe2): utilisateur2,
]</code></pre>

Aucun caractère n’est superflu. Même les parenthèses de clés sont bien de la charge utile, car elles indiquent que la clé est une instance d’objet et non la <span class="css-span">String "groupeX"</span>.

Fonctionnalité 3 : Default getters and setters

Les getters et setters sont facultatifs et ajoutés implicitement sur tous les champs avec le scope par défaut (empty), comme avec les data-classes / value-classes de Lombok.

<pre><code>class Foo {    
     int id
}

var foo = new Foo(id: 1)
assert foo.getId() == 1</code></pre>

Fonctionnalité 4 : Les affectations sont des alias des setters

<pre><code>foo.id = 2
// Dans le bytecode c'est foo.setId(2) qui est appelé</code></pre>

Regardez, si on surcharge le <span class="css-span">setId</span> par défaut et qu’on y place un breakpoint, ce code y passe :

code surcharge setId

Fonctionnalité 5 : Les récupérations sont des alias des getters

<pre><code>def bar = foo.id
// Dans le bytecode c'est foo.getId() qui est appelé</code></pre>

Il est aussi surchargeable.

Fonctionnalité 6 : Un constructeur par défaut avec des paramètres nommés

<pre><code>class Utilisateur {    
     int id    
     String nom
}

var utilisateur = new Utilisateur(id: 1, nom: 'foo')</code></pre>

Ici, les attributs sont publics par défaut, et Groovy devine donc qu'il faut un constructeur avec eux.

De plus, je trouve que pouvoir nommer les paramètres et ainsi les mettre dans l'ordre souhaité manque à Java.

Fonctionnalité 7 : Des arguments de méthode par défaut

Les paramètres de méthodes peuvent avoir une valeur par défaut et ainsi être facultatifs :

<pre><code>String run(String param1, int param2 = 0) {    
     println "$param1 + : $param2"
}

run('Fizz', 8) == 'Fizz : 8'
run('Fizz') == 'Fizz : 0'</code></pre>

On a alors économisé l'écriture de la méthode <span class="css-span">run(String param1)</span>.

Fonctionnalité 8 : Le scope par default est <span class="css-span">public</span>

Plus besoin de spécifier le scope public :

ide public en gris

L’IDE nous l’indique d’ailleurs en le grisant.

Fonctionnalité 9 : Default obvious imports

Plus besoin des imports évidents, ils sont faits implicitement :

  • <span class="css-span">java.lang.\*\</span>
  • <span class="css-span">java.util.\*\</span>
  • <span class="css-span">java.io.\*\</span>
  • <span class="css-span">java.net.\*\</span>
  • <span class="css-span">groovy.lang.\*\</span>
  • <span class="css-span">groovy.util.\*\</span>
  • <span class="css-span">java.math.BigInteger\</span>
  • <span class="css-span">java.math.BigDecimal\</span>

Fonctionnalité 10 : Manipuler des <span class="css-span">File</span> devient simple

Récupérer le contenu texte d’un fichier ? Trop facile. Écrire du texte dans un fichier ? Pareil.

<pre><code>var file = new File("src/main/resources/one.tmpl")
var textContent = file.text
textContent += '''
final line
'''
file << textContent</code></pre>

L'opérateur <span class="css-span"><<</span> est un alias vers <span class="css-span">file.write(textContent)</span>.

En Java c'est un poil plus verbeux :

<pre><code>var path = Paths.get("src/main/resources/one.tmpl");
var textContent = Files.readAllLines(path).join("\n")
byte[] strToBytes = "final line".getBytes();
Files.write(path, strToBytes);</code></pre>

En parlant d'opérateurs...

Facette 3 : Des opérateurs de haut niveau d’abstraction

Groovy est inspiré de Python et Ruby pour ses opérateurs, dont voici mes 7 petits préférés :

1.) Le Spread operator <span class="css-span">*</span>.

Il permet d’invoquer une action sur tous les éléments d’une <span class="css-span">List</span> disposant de cette action.

<pre><code>class Utilisateur {
     void sendMessage(String message) {
          queue.send(this, message)
     }
}

utilisateurs*.sendMessage('unsubscribe')</code></pre>

Ici, chaque <span class="css-span">Utilisateur</span> envoie le message.

En Java, ça aurait donné :

<pre><code>utilisateurs.forEach(utilisateur -> utilisateur.sendMessage("unsubscribe"));</code></pre>

2.) L’equal operator <span class="css-span">==</span>

Oui et alors, on a aussi un equal-equal en Java, non ?

Oui, mais dans le cas de Groovy, si un <span class="css-span">.equals(Object obj)</span> est présent dans la classe, alors cet opérateur en sera un alias. La comparaison de 2 instances similaires sera <span class="css-span">true</span> en Groovy (mais <span class="css-span">false</span> en Java), ce qui est souvent plus intuitif :

<pre><code>import groovy.transform.EqualsAndHashCode

@EqualsAndHashCode
class Foo {
     Integer a
}

var one = new Foo(a: 1)
var two = new Foo(a: 1)
assert one == two</code></pre>

C'est effectivement plus intuitif.

L’égalité de java est transférée sur l’opérateur <span class="css-span">===</span> (on ne voudrait pas perdre une fonctionnalité tout de même).

3.) L'Elvis operator <span class="css-span">?:</span>

Il permet d’affecter une valeur par défaut si l’élément de gauche est <span class="css-span">false</span> (<span class="css-span">null</span>, zéro et vide sont <span class="css-span">false</span> en Groovy).

On évite alors les ennuyants ternaires du genre <span class="css-span">displayName = utilisateur.nom ? utilisateur.nom : 'Anonymous'</span>. Les <span class="css-span">Optional</span>s deviennent ainsi beaucoup moins nécessaires pour la null-safety.

<pre><code>displayName = utilisateur.nom ?: 'Anonymous'</code></pre>

<span class="css-span">utilisateur</span> peut être <span class="css-span">null</span> ? No problem, on en vient donc aux safe-operators.

4.) Les safe operators <span class="css-span">?.</span> et <span class="css-span">?[]</span>

Le premier est le safe navigation operator et le deuxième est le safe index operator. Ils permettent tous deux d’éviter les <span class="css-span">NullPointerException</span>s.

<pre><code>var displayName = utilisateur?.nom ?: 'Anonymous'
var secondBook = utilisateur?.books ?[1] ?: Book.prototype</code></pre>

Avec seulement trois opérateurs, on traite tous les cas de nullité possibles et le one-liner se comprend très bien.

5.) Elvis assignment operator <span class="css-span">?=</span>

Vous avez besoin d’éviter à tous pris la nullité d’une variable ? Initialisez-la seulement quand elle est effectivement <span class="css-span">null</span> avec l’Elvis assignment operator :

<pre><code>var utilisateurDto = utilisateurRestRepository.get('123')
utilisateurDto.nom ?= 'John Smith'</code></pre>

Avec tous les opérateur de null-safety, le principe de convention-over-configuration prend tout son sens.

6.) Range operator <span class="css-span">..</span>

On peut créer des plages d’entiers ou de caractères et itérer dessus directement :

<pre><code>('a'..'g').each { println it } // prints a b c d e f g
(1..5).each { println it } // prints 1 2 3 4 5</code></pre>

On peut aussi se servir des <span class="css-span">range</span> pour sublister une collection :

<pre><code>[1, 2, 3, 4, 5][3..-1] == [4, 5]
[1, 2, 3, 4, 5][0..3] == [1, 2, 3, 4]</code></pre>

7.) Spaceship operator <span class="css-span"><=></span>

C’est un alias de <span class="css-span">.compareTo()</span>

<pre><code>assert ('a' <=> 'd') == -1
assert ('a' <=> 'a') == 0
assert ('g' <=> 'a') == 1</code></pre>

Facette 4 : Des high order functions natives sur les structures de données

L’activité probablement la plus récurrente dans un backend est de manipuler des structures de données. Les API <span class="css-span">Function</span> et <span class="css-span">Stream</span> ont révolutionné cette pratique. Mais l’intelligibilité est encore limitée par la verbosité de Java.

Voyez plutôt ce que ça donne en Groovy :

<pre><code>var entiers = [1, 2, 3]

//collectEntries (convert list to map)
Map<Integer, Utilisateur> utilisateurParId = entiers.collectEntries { [(it): utilisateurRepository.findById(it)] }

//groupBy
entiers.groupBy { it > 2 } == [false: [1, 2], true: [3]]

//split
entiers.split { it > 2 } == [[3], [1, 2]]

//average
entiers.average() == 2

//min with closure
var utilisateurAvecBalanceMin = entiers.min { utilisateurRepository.findById(it)?.balance }

//intersection
entiers.intersect([3, 4, 5]) == [3]

//indexation
entiers.indexed == [0: 1, 1: 2, 2: 3]

//combinations
[[1, 2], ['a', 'b']].combinations() == [[1, a], [2, a], [1, b], [2, b]]

//permutations
entiers.permutations() == [[1, 2, 3], [3, 2, 1], [2, 1, 3], [3, 1, 2], [1, 3, 2], [2, 3, 1]]

//collate (partitionner)
(1..10).collate(3) == [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]</code></pre>

Vous avez saisi l’idée : toute opération de transformation existe nativement dans Groovy, et les mixer entre elles donne des possibilités infinies tout en conservant une lisibilité accrue.

Intégrer Groovy dans un projet Java existant

Il faut configurer la "joint-compilation", pour compiler le Java et le Groovy. L’idéal est d’utiliser <span class="css-span">Gmavenplus</span> :

<pre><code>
&lt;plugin>
     &lt;groupId>org.codehaus.gmavenplus&lt;/groupId>
     &lt;artifactId>gmavenplus-plugin&lt;/artifactId>
     &lt;version>1.13.1&lt;/version>
     &lt;executions>
          &lt;execution>
               &lt;goals>
                    &lt;goal>execute&lt;/goal>
                    &lt;goal>addSources&lt;/goal>
                    &lt;goal>addTestSources&lt;/goal>
                    &lt;goal>generateStubs&lt;/goal>
                    &lt;goal>compile&lt;/goal>
                    &lt;goal>generateTestStubs&lt;/goal>
                    &lt;goal>compileTests&lt;/goal>
                    &lt;goal>removeStubs&lt;/goal>
                    &lt;goal>removeTestStubs&lt;/goal>
               &lt;/goals>
          &lt;/execution>
     &lt;/executions>
     &lt;dependencies>
          &lt;dependency>
               &lt;groupId>org.codehaus.groovy&lt;/groupId>
               &lt;artifactId>groovy-all&lt;/artifactId>
               &lt;version>3.0.12&lt;/version>
               &lt;scope>runtime&lt;/scope>
               &lt;type>pom&lt;/type>
          &lt;/dependency>
     &lt;/dependencies>
&lt;/plugin>
</code></pre>

Si votre prod n'est pas prête à accueillir Groovy, peut-être que votre stack de test l'est. Il suffit alors de configurer la joint-compilation <span class="css-span">Gmavenplus</span> en scope test uniquement avec :

<!-- fs-richtext-ignore --><scope>test<scope>.

Démarrer un projet Groovy from scratch

Plusieurs solutions s’offrent à nous.

1.) Grails (Groovy on Rails)

Grails est un framework du même créateur et maintenu par la même société, Object Computing :

<pre><code>grails create-app myApp
cd myApp
grails run-app</code></pre>

2.) Springboot

Le spring initializr permet de générer un projet groovy :

spring nitializr gui

3.) Micronaut

Idem avec le Micronaut Launch.

micronaut lauch gui

4.) Gradle

On peut aussi faire des applications Groovy pur depuis Gradle :

<pre><code>sdk use gradle 7.5.1
mkdir demo && cd demo
gradle init</code></pre>

gradle init en CLI

Ce qui nous donne cette jolie app Gradle avec une entrypoint class <span class="css-span">App</span> :

structure app avec gradle

Conclusion

Java est un langage verbeux et contraint dans son évolution par le souci de rétrocompatibilité. C'est là que Groovy intervient : il offre une meilleure expressivité et les fonctionnalités des langages modernes, tout ça en restant compatible avec la plateforme JVM. Ce langage est déjà utilisé dans des outils du marché, et commence à pointer le bout de son nez en production dans les applications métier. Il est maintenu par des grands noms du monde Java : Guillaume Laforge, Cédric Champeau, ... et fait partie de l'Apache Software Foundation, ce qui donne confiance dans sa stabilité en production.

Sautez le pas dans vos propres projets (avec une suite de tests convenable) leur apportera productivité et fun.

No items found.
ça t’a plu ?
Partage ce contenu
Antoine

Si ça parle Java chez Younup, soyez sûr qu'Antoine n'est jamais très loin !

Spécialiste de Spring Boot et fan de Groovy - pour son langage intuitif, haut niveau et qui permet la métaprogrammation - il est toujours curieux de découvrir de nouvelles technos ou outils qui pourraient booster sa productivité.

Un peu de chocolat, du café et un pc qui tient la route, c'est tout ce dont il a besoin pour coder ! Pas possible de poireauter 4h par jour devant des loaders...

Mais Antoine n'est pas seulement fan de Bob Martin ou de designs modulaires, laissez-lui un après-midi pour customiser son jardin avec un nouveau cabanon ou karchériser sa terrasse.