⚠️ Lisez cette lettre dans votre navigateur pour profiter des corrections de dernière minute
Les liens marqués avec 🔒 sont accessibles seulement aux membres de M33
Le pôle de formation Craft
Indicateur DDD
Je ne peux pas m’empêcher d’en parler dans chaque lettre.
En ce moment je cherche à remplacer mon vieux script shell qui isole chaque terme du code source par un vrai analyseur syntaxique. Pour cela je m’appuie sur java-parser, un module de prettier-java dont notre excellent Clément D est un contributeur majeur.
En analysant l’arbre syntaxique j’espère tirer encore plus de métriques du code.
On pourrait imaginer qu’un jour (soyons fous !) on obtienne une sorte de “Sonar DDD” qui vous donnera quelques bons tuyaux pour aligner votre code avec votre domaine métier ?
Le vieux script shell est très performant :
$ time make ddd
...
make ddd 0,03s user 0,01s system 63% cpu 0,053 total
et facile à adapter à Kotlin par exemple
Pour la version avec un analyseur syntaxique mes KPI sont :
conserver de bonnes performances (moins de 0,1 s)
ne pas être limité à Java (support de Kotlin & Typescript au moins)
Comment écrire de bons tests unitaires
Chez Sipios les tech-lead sont continuellement formés, à raison d’une séance par mois sur des sujets variés, suivant le format “classe inversée”.
Le formateur peut être un tech-lead, notre Engineering Manager, notre CTO…
Ce mois-ci c’était mon tour 🙂
Le sujet portait sur les tests unitaires.
En effet ce n’est pas trivial de faire de “bons” tests unitaires. Ils doivent être rapides, indépendants, répétables…
La M33 Academy, organisme de formation de M33, propose un standard sur le même sujet. Naturellement il y a beaucoup de similitudes mais également quelques compléments :
Programmation fonctionnelle
Ce mois-ci j’ai également eu le plaisir de faire un talk sur la programmation fonctionnelle.
Ses paradigmes sont radicalement différents de ceux de la programmation orientée objet (fonctions pures et centralisation de l’état VS découpage en objets de l’état et des méthodes qui le transforment) mais ils ont également quelques terrains d’entente (immutabilité, lazyness).
Finalement chaque paradigme a son domaine de prédilection et les langages modernes (Java, Kotlin, Typescript…) permettent de tirer le meilleur parti des deux.
Ce que j’aime bien avec ces talks c’est qu’il y a le plaisir altruiste de partager ses connaissances mais aussi le plaisir égoïste d’en apprendre sur le sujet. En me documentant j’ai eu la chance de découvrir un intervenant jusqu’ici inconnu de moi : Kevlin Henney (non ce n’est pas une faute de frappe 😋) cf “Veille” ci-dessous ⬇️
Améliorer sa pratique
Maven
Convention over configuration
— David Heinemeier Hansson
Maven est un outil bien connu dans l’écosystème Java. On le présente comme un gestionnaire de projet. De mon côté je le vois comme un outil 2 en 1
Gestionnaire de dépendances
Gestionnaire de construction
A la racine du pom.xml (Project Object Model) il y a deux grandes sections
<dependencies>
<build>
chacune permettant de configurer l’un des deux rôles de Maven.
On va s’intéresser ici au rôle de gestionnaire de construction.
Cycle de vie
En fait, Maven se contente de dérouler un cycle de vie.
A chaque phase du cycle il délègue le travail à un plugin (plus exactement à un goal d’un plugin) préalablement enregistré.
Il est aussi possible de demander à Maven d’exécuter un goal directement, sans dérouler de cycle.
Plugins
Chaque plugin peut avoir un ou plusieurs goals. C’est une tâche spécialisée que le plugin propose. Par exemple le plugin maven-compiler-plugin propose un goal pour compiler le code de production et un autre pour compiler les tests.
Le goal d’un plugin se note <plugin>:<goal>
où <plugin>
désigne les coordonnées du plugin. Notez que le groupId
et la version
peuvent être omis.
De plus lorsque le plugin se nomme maven-xxx-plugin
son nom peut être abbrevié.
Par exemple le goal clean
du plugin maven-clean-plugin
peut se noter :
org.apache.maven.plugins:maven-clean-plugin:3.2.0:clean
maven-clean-plugin:clean
clean:clean
En ligne de commande il est plus pratique d’utiliser les noms abbréviés 🙂
⚠️ Notez la différence :
$ mvn clean
(iciclean
fait référence à la phase “clean” du cycle “clean”)
$ mvn clean:clean
(ici,clean:clean
fait référence au goal “clean” du plugin “maven-clean-plugin”)
Dans les logs les goals sont affichés dans une forme intermédiaire (le groupId est omis mais la version est précisée) ici en vert :
Par défaut certains plugins sont déjà enregistrés auprès de certaines phases.
Par exemple :
Phase clean : clean:clean
Phase compile : compiler:compile
Phase test : surefire:test
Etc.
Configuration des plugins
Chaque plugin peut se configurer dans la section <build><plugins>
du POM.
Dans cette section (contrairement à la section <depedency>
) le groupId
par défaut est org.apache.maven.plugins
.
Autrement dit vous pouvez omettre le groupId
si vous configurez un plugin officiel du projet Maven :
<build>
<plugins>
<plugin>
<!-- pas besoin du groupId -->
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
...
</plugin>
</plugins>
</build>
La plupart des plugins définissent une phase pour chacun de leurs goals, il n’y a donc pas besoin de la configurer explicitement.
Prenons l’exemple du goal integration-tests du plugin failsafe
:
On y découvre entre autres :
Le nom complet du goal :
org.apache.maven.plugins:maven-failsafe-plugin:3.0.0:integration-testLa phase à laquelle ce goal est attaché : integration-test
Prenons un autre exemple : assembly:single
Ici aucune phase n’est défnie par défaut. Si on souhaite que Maven exécute ce plugin à chaque construction de notre projet nous allons devoir le configurer dans le POM pour l’attacher à une phase :
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>assembly-goal-execution-id</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
Ainsi, à chaque fois que Maven passera par la phase “package” du cycle de vie “default” il donnera la main au goal assembly:single
.
Si plusieurs goals sont attachés à la même phase, ils s’exécutent dans le même ordre que leur déclaration dans le POM.
Mojo
Parfois vous croiserez le terme “Mojo”. Il s’agit de la version Maven de “Pojo”
Pojo = Plain Old Java Object (on n’estimera jamais assez l’influence de Martin Fowler)
Mojo = Maven plain Old Java Object
Il s’agit de l’implémentation en Java d’un plugin Maven.
Conclusion
Nous avons vu le principe des cycles de vie et comment les plugins s’y accorchent.
Mon intention était de plonger bien plus profondément dans les entrailles de Maven mais finalement le sujet est vaste et j’y reviendrai probablement dans les prochaines newsletters.
Veille
Par un improbable hasard, Nicolai Parlog a quasiment publié la réponse à l’énigme dans ce post très complet sur les nouveautés de Java 20.
Quelques sujet qui m’excitent particulièrement : le pattern-matching et les threads virutels, mais il y en a vraiment pour tous les goûts :
Ce mois-ci j’ai découvert Kevlin Henney à l’occasion de ma documentation sur la programmation fontionnelle (cf plus haut ⬆️).
Je propose d’adopter sa suggestion de renommer dans nos outils de gestion de projet la colonne “Bugs” en “Rêves brisés“ ou “Illusions fracassées” :
IntelliJ IDEA 2023.1 est sorti ! Mais qu’attendez-vous pour vous jeter dessus ?
Pléthore de nouvelles features of course. Je note en particulier :
Possibilité d’extraire une méthode d’un bloc qui retourne plusieurs variables
Amélioration des perfs de l’import des projets Maven
Formattage des tables Markdown
Meilleure inspection du code Java
Support de Java 20
Puzzler du jour
Réponse au challenge précédent
La dernière fois nous nous demandions pourquoi le code suivant compile aux lignes 3 et 9, mais pas à la ligne 6 :
1 public class CastSurprise {
2 public static void main(String... args) {
3 char c1 = 'a' + 5; // Ici ça compile !
4
5 char c2 = 'a';
6 c2 = c2 + 5; // Là ça compile pas…
7
8 char c3 = 'a';
9 c3 += 5; // Mais là ça re-compile !?
10 }
11}
En effet à bien y regarder, ces lignes font toutes la même chose : affecter à un char
la valeur ‘a’ + 5
(c’est à dire ‘f’
)
La règle générale c’est qu’une narrowing conversion (convertir un type int
en char
par exemple) doit être castée explicitement.
C’est pour ça que l’affectation de c2 ne compile pas.
Cependant il y a quelques exceptions :
Lorsque l’expression est compile-time constant (par exemple
‘a’ + 5
) alors son type est déterminé par le contexte :
In addition, if the expression is a constant expression (§15.29) of type
byte
,short
,char
, orint
:
A narrowing primitive conversion may be used if the variable is of typebyte
,short
, orchar
, and the value of the constant expression is representable in the type of the variable.https://docs.oracle.com/javase/specs/jls/se17/html/jls-5.html#jls-5.2
L’expression ‘a’ + 5
produit un int
de valeur 102. Un char
accepte toute valeur comprise entre 0 et 65535 (inclus). 102 peut donc être affecté à c1 sans problème pour représenter ‘f’
.
Les opérateurs composés (+=, -=, etc.) incluent un cast implicite :
A compound assignment expression of the form
E1 op= E2
is equivalent toE1 = (T) ((E1) op (E2))
, where T is the type ofE1
, except thatE1
is evaluated only once.https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.26.2
c3 += 5
est équivalent à c3 = (char) (c3 + 5).
Challenge du jour
Si vous avez suivi le challenge de la première newsletter, celui-ci sera facile 🙂
D’après vous, qu’affiche ce petit code Java ? Savez-vous l’expliquer ?
class DateTimeWeirdness {
public static void main(String[] args) {
ZonedDateTime twoThirtyInTheMorning = ZonedDateTime
.of(2023, 3, 26, 2, 30, 0, 0, ZoneId.of("Europe/Paris"));
ZonedDateTime threeInTheMorning = ZonedDateTime
.of(2023, 3, 26, 3, 0, 0, 0, ZoneId.of("Europe/Paris"));
boolean right = twoThirtyInTheMorning.isBefore(threeInTheMorning);
System.out.printf("2:30 AM is before 3:00 AM, right ? %b", right);
}
}