⚠️ 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
Je serais plus opportun d’attendre l’année prochaine mais je n’ai pas la patience 😇
Savez-vous d’où vient le mot “bissextile” ?
Vous le savez déjà si vous avez fait le kata Roman Numerals, les romains ADORENT se compliquer la vie. Je pense que c’est pour ça qu’ils avaient une administration aussi performante.
Pour compter les jours ils ont poussé loin le raffinement. Ils décomptaient les jours avant des dates clés du mois : les calendes (premier jour du mois), les nones (car neuf jours avant les ides), les ides (au milieu du mois).
On ne disait pas “1er février, 2 février, 3 février” etc. mais “Calendes de février, 4è jours avant les nones, 3è jour avant les nones” etc.
En fin de mois on ne disait pas “24 février, 25 février” mais “6è jour avant les calendes de mars, 5è jour avant les calendes de mars”
Lorsqu’il a fallu ajouter un jour intercalaire, les romains ne se sont pas contenté d’ajouter un jour avant les calendes de mars (ce serait bien trop simple). Au lieu de cela ils ont dupliqué le 24 février !
Ainsi le mois de février romain avec un jour intercalaire a un 24 et un 24 bis.
Et là vous voyez venir le truc : le 24 février se dit “6è jour avant les calendes de mars” et donc le 24 bis se dit “6è jour bis avant les calendes de mars” soit en latin “ante diem bis sextum Kalendas Martias”
Et voilà d’où vient le mot bissextile !
Revenons à nos moutons :
Le pôle de formation Craft
Indicateur DDD
Après quelques tâtonnements que vous avez pu suivre dans les newsletters précédentes, l’indicateur DDD est arrivée à une maturité satisfaisante.
On pourrait bien sûr le raffiner à l’infini et certains axes d’amélioration sont en cours d’investigation mais l’heure est venue pour l’indicateur de quitter le labo et faire ses premiers pas dans le grand monde.
On sait maintenant interpréter la courbe. Deux paramètres sont importants :
La hauteur atteinte à droite du graphique (ici 30%) qui indique le taux total de termes du lexique ubiquitaire dans le code
La “vitesse” à laquelle la courbe atteint ce score final (qu’on pourrait modéliser par l’aire sous la courbe) qui donne une indication sur la répartition des mots du lexique
Le challenge est maintenant de montrer sa valeur au métier afin qu’il soit adopté comme indicateur de qualité, par exemple dans nos slides 4S (Stability, Security, Speed, Sustainability)
What gets measured gets improved.
Formation Code Smell
Il n’y a pas secret : pour s’améliorer il faut pratiquer ! Les transports font office de pomodori naturel. Je vous propose de consacrer votre prochain transit aux katas qui se trouvent en fin de formation sur les Code Smells
⚠️ Pensez à soumettre le formulaire à la fin de chaque kata, c’est ainsi que je mesure l’audience. C’est important pour moi, merci !
Pair programming
Tous les mercredi matin j’ai un créneau réservable de pair-programming.
Outre que c’est toujours un bon moment à passer je remarque que les devs s’améliorent dans la maîtrise de leur IDE et en TDD, deux sujets qui me tiennent à cœur ❤️
Améliorer sa pratique
Misconceptions sur les tests unitaires
Beaucoup de devs débutants sont bourrées de fausses croyances sur les tests unitaires. Mais qu’apprennent-ils à l’école ?
❌ L’unité du test unitaire c’est la fonction (une fonction ⇔ un test)
C’est naturellement faux ! Pour une fonction vous avez plusieurs tests pour vérifier plusieurs cas d’usage, vérifier son comportement avec des paramètres invalides etc.
✅ L’unité du test unitaire c’est la règle de gestion
❌ Toute méthode doit être testée (même les méthodes privées)
Symptôme : des méthodes package-private1 qui ne sont référencée que dans leur classe et ses tests. Si vous refactorez votre classe vous devrez modifier les tests. Or c'est la stabilité des tests qui garantit qu'un refactoring n'a pas modifié le comportement de la classe !
✅ On ne teste que le comportement public de la classe
❌ Mon test d’intégration doit interagir avec un système externe à mon projet
Si vos tests nécessite qu’un SGBD soit préalablement démarré, vous encouragez vos collègues et vous même à laissiez tomber l’exécution régulière des tests. Si un test dépend d’un service externe et que celui-ci est indisponible, le tests échouera alors que le problème n’est pas chez vous.
✅ Utiliser une BDD mémoire, Testcontainers ou MockServer pour isoler vos tests et que leur démarrage ne nécessite aucune cérémonie. Lancer les tests doit être signle-click (ou single-shortcut 😉)
Clés d’internationalisation (i18n) type-safe
Si votre frontend est conçu pour être multilingue, tous les textes, libellés etc. doivent être codés comme des clés de référence dans une table de traduction, laquelle table est dynamiquement sélectionnée en fonction de la langue choisie par l’utilisateur.
Dans le projet que nous considérons nous utilisons Transloco pour gérer l’i18n.
Si une clé n’existe pas dans la table de traduction (faute de frappe de la dev, réorganisation de la table…) l’utilisateur verra quelque chose comme ça :
La clé de traduction à la place du texte.
Pour mitiger ce risque notre CI est équipé de Danger qui attire l’attention des devs sur les erreurs de configuration ou (en l’occurrence) de fichiers de traduction
Bien sûr avant d’envoyer son code sur la CI, la dev peut tester sur son poste et constatera l’erreur si elle visite la bonne page et a le bon coup d’œil !
Et si on pouvait mettre en place un poka-yoke pour que l’IDE signale l’erreur au moment où vous l’écrivez ?
Paul B nous a gratifié de sa maestria du type-system de TypeScript pour nous fournir un tel système. Tout d’abord il déclare le type suivant :
type FlattenObjectKeys<T extends Record<string, unknown>, Key = keyof T> = Key extends string
? T[Key] extends Record<string, unknown>
? `${Key}.${FlattenObjectKeys<T[Key]>}`
: `${Key}`
: never;
Ensuite il n’y a plus qu’à augmenter le module '@ngneat/transloco'
declare module '@ngneat/transloco' {
type TranslocoKey = FlattenObjectKeys<typeof import('assets/i18n/fr.json')>;
declare class TranslocoService {
translate(x: TranslocoKey, params?: HashMap): string;
/**
* @deprecated Your key is wrong. Please check the 'fr.json' translation file.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
translate(yourKeyIsWrong: string, ...args: any): unknown;
}
}
Et le tour est joué !
Entre nous je n’y entrave que pouic mais il semble que l’astuce consiste à charger les clés de traduction dans un arbre de Records puis à définir le type FlattenObjectKeys
qui construit une union de strings à partir de cet arbre.
Lorsqu’on se trompe dans la clé de traduction on obtient un warning dans l’IDE :
Avec les bons git-hooks les devs ne peuvent pas commiter un code erroné !
Et en bonus on gagne l’auto-complétion sur les clés de traduction !!
Veille
Une pause café avec José Paumard ça ne se refuse pas ! Au menu : des apprentissages surprenants sur les comparateurs en Java
Puzzler du jour
Réponse au dernier challenge
Toute personne normalement constituée examinant cette expression :
['10', '10', '10'].map(parseInt)
Se dirait “Ok, on un tableau de strings, on le map vers un autre tableau en appliquant parseInt
à chacun des éléments. parseInt
prend une string et la convertit en nombre. Donc je vais obtenir un tableau de trois nombres égaux à 10”.
Vous exécutez l’expression et patatras
[ 10, NaN, 2 ]
La première chose que vous pensez alors est “JavaScript c’est vraiment de la 💩”
Mais comme vous êtes ingénieur vous voulez comprendre.
Voici une définition de fonction en JS : f = (x, y) => x * y + 1
Dans cette définition, x
et y
sont des paramètres.
Voici une invocation (ou appel) de fonction : f(1, 2)
Dans cette expression, 1
et 2
sont les arguments de l’appel.
Dans beaucoup de langage, une fonction a un certain nombre de paramètres (son arité) et on applique les arguments à chaque paramètre dans l’ordre. Vous pouvez par exemple définir plusieurs fonctions de même nom mais d’arité différentes (surcharge), et le compilateur pourra sélectionner la bonne fonction à partir du nombre d’argument de l’appel.
En JS ce n’est pas tout à fait la même chose.
En JS toute fonction est unaire (d’arité 1). Sous le capot, les paramètres que la développeuse déclare sont rassemblés dans un tableau qui est l’unique paramètre de la fonction !
C’est pourquoi en JS vous ne pouvez pas surcharger de fonction.
Le mapping entre les arguments et les paramètres se fait par déstructuration :
[x, y] = [1, 2]
Sachant cela, examinons plus attentivement les fonctions map et parseInt
map((element, index, array) => { … })
Hoho ! map
passe trois arguments à sa callback
parseInt(string, base);
De son côté parseInt
a deux paramètres ! On affecte donc ces paramètres ainsi :
[string, base] = [element, index, array]
Vous avez tout compris : l’index de chaque élément du tableau devient la base dans laquelle la string est parsée.
‘10’ base 0 donne 10 car la doc de parseInt indique :
Si la base fournie vaut
undefined
ou 0, le moteur JavaScript procédera comme suit :Si l'argument
string
commence avec "0x" ou "0X", la base considérée sera la base 16.Si l'argument
string
commence avec "0", la base considérée sera la base 8 (octale) ou la base 10 (décimale). La base exacte qui sera choisie dépendra de l'implémentation.Si l'argument
string
commence avec une autre valeur, la base considérée sera la base 10.
‘10’ base 1 donne NaN (on ne peut représenter aucun nombre en base 1)
‘10’ base 2 donne 2
Challenge du jour
Refermons le capot de JavaScript et ouvrons celui de Java en observant le code suivant :
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}
Le challenge : pouvez-vous expliquer pourquoi le code compile aux lignes 3 et 9, mais pas à la ligne 6 ?
La réponse au prochain numéro !
Oui je fais de l’auto-promotion sans vergogne 😛