Clean Code : survol initiatique du livre d’Uncle Bob

on 1 June 2021 EVENT and Tags: , , , , , , , with 0 comments
Affiche des diapositives du meetup Clean Code : survol initiatique

Le meetup Clean Code

En ce début juin, trois passionnés de code propre nous ont fait un survol des concepts présentés dans le livre Clean Code. L’auteur est Robert C. Martin, dit Uncle Bob, très connu dans le milieu du Software Craftmanship.

Le livre offrant énormément de conseils divers et variés, ce meetup / BBL de 30 minutes n’a servi que de survol initiatique pour ceux qui seraient intéressés à en découvrir plus.
Nous nous sommes concentrés sur quelques principes de bases, sur le nommage et le découpage en fonctions courtes et en petites classes.
Le tout était agrémenté d’un exemple de nettoyage de code.

Cet article résume une présentation orale lors d’un meetup.
Plus de détails sont disponibles dans les slides en bas de l’article.

Pourquoi Clean Code ?

En tant que développeurs, nous passons le plus clair de notre temps à lire du code. Que ce soit pour le debugger, pour l’améliorer, pour comprendre les impacts d’une modification, etc. L’écriture du code ne représente finalement qu’une petite partie de notre temps.

C’est pour cela que l’on doit prendre le temps d’écrire un code le plus compréhensible et le plus facile possible. Le code doit être propre : clean. Il doit “transpirer” le vocabulaire métier et ses règles.

Sans cela, le code deviendra un cauchemar à maintenir, au fur et à mesure qu’il évoluera.
Il deviendra technique et inutilement complexe, donc peu malléable et fragile à modifier.

Principes du Clean Code

Voici quelques principes simples qu’on va appliquer en produisant du code clean.

KISS : Keep It Simple Stupid

La solution développée doit être la plus simple pour répondre au besoin demandé.
Le maître mot est ici de ne pas faire d’over-engineering.
Il ne faut pas complexifier inutilement le code.

À l’inverse, le code ne doit pas être plus simple que nécessaire.
Il faut savoir jauger le juste milieu.

Voir plus sur la page Wikipedia.

DRY : Don’t Repeat Yourself

Le code ne doit pas répéter deux fois une règle métier.
Chaque connaissance métier ne doit être codée qu’à un seul endroit logique.

Cela passe bien sûr par de l’extraction et de la réutilisation de méthodes à la place de deux codes identiques. Mais aussi par l’extraction de deux bouts de code différents mais qui réalisent fonctionnellement le même calcul, voir l’opposé.

Par exemple, deux constantes qui encodent le même concept mais avec deux représentations différentes devraient être fusionnées en une seule constante + une méthode qui permet de calculer la seconde représentation.

Le but est d’avoir le moins de surprise en modifiant le code : chaque information ou traitement qui pourra évoluer plus tard ne doit être modifié qu’à un seul endroit dans le code.

Attention : on parle bien de non-duplication de connaissances, et non de code. Deux méthodes qui ont la même implémentation ne doivent pas forcément être mutualisées si elles réalisent en fait deux traitements différents. Elles se trouvent juste être identiques à un instant t, mais par hasard. Les deux méthodes doivent rester maintenables indépendamment pour éviter toute régression surprise le jour où l’un des traitements évolue seul.

Voir plus sur la page Wikipedia.

YAGNI : You Aren’t Gonna Need It

L’expérience a montré que la prévision de l’avenir était toujours risquée, et souvent fausse. Il ne faut pas prévoir les évolutions futures du code. Partez du principe qu’on n’aura pas besoin de ce qui n’est pas encore demandé. Il faut éviter de créer des abstractions prématurées en devinant comment les spécifications fonctionnelles pourraient évoluer. On a toutes les chances de se tromper, et d’avoir perdu du temps à créer ces abstractions qu’il va falloir retirer le jour où le vrai besoin sera connu, et à l’opposé de nos divinations…

Voir plus sur la page Wikipedia.

La règle du boy-scout

Après avoir exploité un terrain pour en faire un camp, les scouts prennent toujours le temps de le nettoyer. Ils le rendent au moins aussi propre qu’ils l’ont trouvé, si ce n’est plus propre encore.

En développement, on profitera toujours des nouvelles fonctions ou corrections de bugs pour refactorer ce qui le nécessite, et ainsi laisser le code plus propre qu’on ne l’a trouvé.

Petit à petit, même un projet legacy avec du code spaghetti peut devenir de plus en plus propre.
Il peut même perdre son qualificatif “legacy code“, dans le sens où les développeurs peuvent ensuite être fier de montrer leur production. Et dans le sens où de nouveaux développeurs pourront comprendre facilement le code. Et le modifier sans crainte de tomber dans des pièges à régressions.

Voir plus sur la page InformIT.

Méthodologie Clean Code à suivre

Rendre un code propre, c’est le rendre aussi lisible que la spécification fonctionnelle.
Le code restera technique, bien sûr, mais il faut tendre vers le moins de technique possible.
Idéalement, le code devrait être une simple traduction du français vers le Java, par exemple.
On devrait pouvoir montrer le code au Product Owner, en faisant une traduction littérale la plus simple possible pour qu’il ou elle puisse valider que le code fait bien ce qui est demandé.

Pour cela, la première étape est le nommage.
On doit rester aussi proche du domaine métier que possible.

Une fois le nommage bon, la seconde étape est un découpage en petites fonctions.
Cela permet de réduire la complexité d’appréhension du code.

Enfin, la troisième étape, une fois qu’on se retrouve avec beaucoup de petites fonctions, est de répartir ces fonctions dans de petites classes. On découpe les classes jusqu’à ce qu’elles aient une taille raisonnable et n’aient qu’une seule responsabilité. Cela facilite la lecture, la compréhension et la validation du code.

Voyons voir ces trois étapes.

Étape 1 : le nommage

La base du Clean Code est un bon nommage des concepts manipulés par le projet.
C’est ainsi une première étape incontournable pour rendre le code aussi transparent que possible :

  • Pas d’abréviations ni de noms imprononçables : il faut que le code puisse être recherché facilement, compréhensible sans ambiguïté, et mentionable facilement dans des discussions ;
  • Sans parasites : ne pas polluer le code de mots fourre-tout qui n’ajoutent aucune valeur à la compréhension du code. Ne pas non plus utiliser de préfixes ou suffixes techniques. Ne conserver que l’essence même de ce que manipule le code ;
  • Révéler l’intention : le nommage d’une variable, d’une méthode ou d’une classe doit expliquer pourquoi elle existe et comment l’utiliser, plutôt que d’expliquer comment elle exécute sa tâche. Le “comment” est un détail technique d’implémentation qui se déduit en lisant le code, et dont on doit s’abstraire quand on utilise ledit code. Au contraire, on doit comprendre le “pourquoi” pour pouvoir utiliser ledit code ;
  • Expliquer les concepts : cohérent & cherchable : utiliser des mots communs pour ce qui se ressemble. Utiliser des mots différents pour ce qui ne doit pas être confondu. Avoir un glossaire commun au projet et au métier ;
  • Déplacer les commentaires dans les noms et / ou le typage : les commentaires finissent toujours par devenir obsolète, et ne sont pas visibles à l’endroit où on utilise les variables ou méthodes. Tandis que les noms de ces variables ou méthodes sont bien entendu visibles là où on les utilise : on peut tirer parti de ce fait pour qu’elles s’auto-documentent…

Une fois les variables nommées de façon descriptive, on va se rendre compte que certains concepts peuvent être extraits en méthodes puis en classes : c’est ce qu’on verra par la suite.

Étape 2 : découpage en petites fonctions

Le Clean Code nous propose de suivre ces heuristiques simples pour produire des fonctions facilement compréhensibles et maintenables :

  • Faire des fonctions courtes : pour qu’elles soient plus facilement compréhensibles, et aussi réutilisables, ce qui permet de ne pas dupliquer une connaissance à plusieurs endroits ;
  • Nommer les fonctions avec un seul verbe : sinon ça veut dire que la fonction fait plusieurs choses, violant la règle qu’on vient de voir ;
  • Avoir le moins d’arguments de fonction possible : moins il y a d’arguments, moins il y a de combinatoires possibles pour la fonction. Donc elle sera plus facile à appréhender et à tester ;
  • Éviter les arguments boolean : un booléen traduit généralement le fait que la méthode fait deux choses, une chose quand true, une autre quand false. Il vaut mieux couper la méthode en deux, chacune ne gérant qu’un cas ;
  • Avoir un seul niveau d’abstraction par fonction : le nom d’une fonction doit être un seul niveau d’abstraction plus haut que le code de la fonction. Si le code de la fonction à plusieurs niveaux d’abstractions, il est préférable de découper la fonction afin que chaque appel de sous-fonction descende le niveau d’abstraction ;
  • Écrire les fonctions appelées sous la fonction appelante : suivre la métaphore de l’article de presse permet au lecteur du code d’y naviguer plus rapidement pour trouver ce qu’il cherche (la source potentielle d’un bug, ou l’endroit où brancher une nouvelle fonctionnalité) sans forcer le lecteur à lire tout le code sans rapport avec ce qu’il ou elle cherche…

Étape 3 : organisation des fonctions en petites classes

Le Clean Code met l’accent sur les principes SOLID en POO.

Par souci de temps, nous ne nous sommes attardé que sur la première lettre de cet acronyme : Single Responsibility Principle.

Une classe ne doit changer que pour une seule raison. Ce qui pousse à découper en plus petites classes, avec peu de champs, et ainsi à maintenir la cohésion des champs au sain d’une classe.

Une classe a une bonne cohésion quand presque toutes les méthodes utilisent tous les champs de cette classe. De même, on a une bonne cohésion quand tous les champs de la classe sont utilisés par presque toutes les méthodes. Si on a deux groupes distincts de champs + méthodes, c’est un bon indice pour couper la classe en deux. Ou en trois, si on a besoin d’une classe qui regroupe les deux concepts. On utilisera ici une composition des deux autres classes plutôt qu’un héritage.

Refactoring vers un code plus propre

Tout au long de la présentation, nous avons montré le refactoring d’un code d’exemple en fil rouge pour le rendre plus propre.

L’exemple consistait à mapper une ligne de base de données vers un objet Java.
Une ligne a un libellé et un intervalle de validité dans le temps.
Elle peut être supprimée (historisée) et affichée grisée.
Ou elle peut être active à la date du jour : elle est affichée en surbrillance jaune.

La première étape consiste à bien nommer les classes, variables, etc. :

  • Les abréviations comme “lbl”, “dt” et “rs” sont transformées en “label”, “date” et “resultSet” ;
  • La class “HistoryData” est renommée “HistoryRow” pour éviter le parasite fourre-tout “Data” ;
  • L’intention est précisée en renommant “date” en “today” ;
  • Par cohérence avec la constante “START_DATE”, “beginDate” est renommé en “startDate”.

La seconde étape consiste à découper en petites fonctions.
Pour savoir comment découper, on a identifié toutes les responsabilités et connaissances du code.
Les responsabilités de l’unique classe avec une unique méthode sont ainsi :

  • Noms et types des colonnes en base ;
  • Transformation des valeurs en base vers des valeurs Java ;
  • Parsing des dates au format de l’application (format ISO-8601) ;
  • Gestion des exceptions ;
  • Règle métier : quand une ligne est-elle considérée comme active ? ;
  • Connaissance de la façon d’interpréter un intervalle de dates.

La classe initiale a été coupée en trois petites classes ayant chacune une responsabilité différente.
Et chaque classe est découpée en plusieurs courtes méthodes dont chacune ne s’occupe que d’une action, à un seul niveau d’abstraction.

Plus en détails

Vous trouverez bien sûr de plus amples détails dans les slides de la présentation :