Xavier Lamorlette

“Clean Code”

Notes de lecture sur le livre “Clean Code: A Handbook of Agile Software Craftsmanship” de Robert C. Martin, alias “Uncle Bob” (disponible sur Safari et Amazon).

Clean Code book

Nommage (“2 Meaningful Names”)

Prendre du temps pour choisir les noms avec soin.

Ne pas hésiter à faire des renommages.

Un long nom est toujours préférable à un commentaire.

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.

La longueur des noms des méthodes est inversement proportionnelle à la portée : on peut se permettre des noms d'autant plus longs que la portée est restreinte.

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. Et ne pas avoir d'effet de bord.

Remplacer les switchs par du polymorphisme. Si ce n'est pas possible, mettre le switch dans une fonction de bas niveau qui ne fait que cela.

Mettre le plus petit nombre d'arguments possibles (0 : « niladic » ; 1 : « monadic » ; 2 : « dyadic » ; 3 : « triadic »).

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

Pour réduire le nombre d'arguments, on peut créer des objets.

Ou une méthode sur l'objet à modifier.

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.

Toujours préférer un nom de variable ou de fonction à un commentaire.

Formattage (“5 Formatting”)

Ordonner les fonctions pour pouvoir lire le fichier de code de haut en bas.

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 ; entre les instructions d'une fonction.

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

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 janvier 2019.

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.