Xavier Lamorlette

“Clean Code”

Notes de lecture sur le livre “Clean Code: A Handbook of Agile Software Craftsmanship” de Robert C. Martin.

Clean Code book

Sommaire :

Préface (“Foreword”)

« L'honnêteté dans les petites choses n'est pas une petite chose. »

La perfection est dans les détails.

« La cohérence du style distingue l'excellence de la simple compétence. »

La refactorisation fait partie du concept de « fini ».

Il faut être honnête à propos de l'état du code.

Code propre (“1 Clean Code”)

Des spécifications pouvant être exécutées par une machine sont du code. Écrire de telles spécifications est de la programmation. Des besoins bien spécifiés sont aussi formels que du code.

Loi de LeBlanc : « Plus tard signifie jamais. »

Le code propre se lit comme de la prose bien écrite.

Boy scout rule : laisser le terrain plus propre que trouvé en arrivant.

Nommage (“2 Meaningful Names”)

Prendre du temps pour choisir les noms avec soin. Ne pas hésiter à faire des renommages.

Ne rien laisser d'implicite.

Prohiber les préfixes inutiles.

Éviter les abrévations et les noms imprononçables.

Classes et objets : utiliser des noms. Méthodes : utiliser des verbes.

Le nommage doit permettre de lire le code comme des phrases correctement construites.

Fonctions (“3 Functions”)

Les fonctions doivent être les plus courtes possible.

Ne pas dépasser deux niveaux d'indentation.

Une fonction ne doit faire qu'une seule chose. Une fonction ne doit pas avoir d'effet de bord.

Utiliser un seul niveau d'abstraction dans une fonction, ne pas en mélanger plusieurs.

S'il y a plusieurs sections dans une fonction, c'est qu'il faut les extraire.

Avoir le moins possible d'arguments. Pour réduire leur nombre, on peut : créer des objets ; créer une méthode sur l'objet à modifier.

Ne pas utiliser des arguments de sortie mais plutôt la valeur de retour.

Pas d'argument drapeau, ou d'argument sélecteur qui détermine le comportement de la fonction : celà signifie que la fonction fait plusieurs choses.

“Command query separation” : une fonction ne doit pas faire une action et répondre à une question.

Commentaires (“4 Comments”)

Limiter les commentaires au strict nécessaire.

Un long nom (de variable ou de fonction) est toujours préférable à un commentaire.

Formattage (“5 Formatting”)

Ordonner les fonctions pour pouvoir lire le fichier de code de haut en bas : chaque fonction doit introduire et utiliser les fonctions qui viennent ensuite.

Définir les variables et les fonctions au plus proche de leur utilisation.

Objets et structure de données (“6 Objects and Data Structures”)

Loi de Déméter : Principe de connaissance minimale : un module (fonction) ne doit pas connaitre les détails internes des entités qu'il manipule.

Ne pas faire de “train wreck” : cascade d'appels sur les objets retournés par les méthodes.

DTO = Data Transfer Object : classe avec uniquement des membres publics (et strictement rien d'autre).

Active Record : DTO + quelques méthodes (par exemple save / find).

Ne pas faire d'hybrides : à la fois objets et structures de données.

Les objets exposent leurs comportements et cachent leurs données. Ils permettent de facilement ajouter de nouveaux types sans changer les comportements existants.

Les structures de données exposent leurs données et n'ont pas de comportement. Elles permettent de facilement ajouter de nouveaux comportements sans changer les données existantes.

Gestion des erreurs (“7 Error Handling”)

Utiliser les exceptions plutôt que de retourner des erreurs.

Isoler la gestion des exceptions (try / catch) dans des fonctions dédiées (car une fonction ne doit faire qu'une chose).

Définir les exceptions en fonction de ce qui est utile au code appelant.

Ne pas retourner null. Plutôt lever une exception ou retourner un objet dédié à la gestion du cas particulier (“special case pattern”).

Frontières (“8 Boundaries”)

Pour intégrer du code tiers, commencer par écrire des tests pour apprendre à l'utiliser. Ces tests permettent par ailleurs de vérifier ultérieurement le comportement de nouvelles versions du code tiers.

Tests unitaires (“9 Unit Tests”)

Test Driven Development (TDD) : développement dirigé par les tests :

Le code de test doit suivre les mêmes exigences de propreté que le code de production, afin d'être lisible et maintenable. Cela permet de retravailler sereinement le code de production.

Valider un seul concept par test.

Les tests doivent étre “FAST” :

Classes (“10 Classes”)

Single Responsibility Principle (SRP) : principe de responsabilité unique : une classe doit avoir une seule raison de changer.

Maximiser la cohésion : chaque méthode doit utiliser tous les membres.

À l'inverse, minimiser le couplage entre les classes.

Si un sous-ensemble de méthodes utilise un sous-ensemble de membres, extraire une classe.

En découpant une fonction, plutôt que de passer les variables en paramètres, les mettre en membres, puis éventuellement extraire une classe.

Open-Closed Principle (OCP) : une classe doit être ouverte aux extensions (par héritage) mais fermée aux changements (on ne doit pas avoir besoin de la changer).

Dependency Inversion Principle (DIP) : principe d'inversion de la dépendance : une classe doit dépendre d'abstractions (interfaces) pas d'implémentations. Cela permet d'isoler les changements en découplant les classes.

Systèmes (“11 Systems”)

Dependency Injection (DI) : injection de dépendance : déléguer l'instanciation des dépendences via des setters ou des paramêtres du constructeur. C'est une application de l'inversion de contrôle (Inversion of Control: IoC).

Simultanéité (“13 Concurrency”)

Pour la simultanéité, découpler le « comment » du « quand ».

Séparer le code gérant la simulanéité du reste (principe de responsabilité unique).

Limiter au maximum l'accès aux données (et donc leur étendue) pouvant être partagées.

Travailler sur des copies des objets (quittent à les synchroniser ensuite si nécessaire).

Partionner les données en sous-ensembles indépendents pouvant être traités par des threads indépendants.

Prévoir l'arrêt propre du système des éléments simultanés.

Mauvaises odeurs et heuristiques (“17 Smells and Heuristics”)

Une seule commande construire le projet. Une seule commande pour exécuter les tests.

Un seul langage par fichier.

Implémenter tous les comportements évidents attendus d'une classe.

Chaque duplication de code est une occasion manquée d'abstraction.

Remplacer un switch répété plusieurs fois par du polymorphisme.

Ne pas mélanger les niveaux d'abstraction entre une classe et ses dérivées.

Une classe ne doit pas dépendre de ses dérivées (sauf cas particulier).

Faire les interfaces les plus petites possibles, notamment pour limiter le couplage.

Ne pas coupler ce qui ne le nécessite pas, en mettant des définitions à l'endroit le plus pratique par flemme.

Lorsque l'on déclare une fonction statique, s'assurer que l'on aura pas besoin ultérieurement de polymorphisme.

Encapsuler les logiques booléennes des conditions dans des fonctions dédiées.

Expliciter le couplage temporel en forçant l'usage des résultats intermédaires pour les étapes suivantes.

Mettre les constantes de configuration aux niveaux les plus élevés.

Lorsque l'on trouve un bug, faire des tests exhaustifs sur le code autour du bug pour trouver en d'autres.

La dernière mise à jour de cette page date de novembre 2023.

Le contenu de ce site est, en tant qu'œuvre originale de l'esprit, protégé par le droit d'auteur.
Pour tout commentaire, vous pouvez m'écrire à xavier.lamorlette@gmail.com.