Temps de lecture estimé : 20 minutes

Cet article est SUPER long ! Pense à le mettre en favoris ⭐️

Clean Code : Créer du code de qualité, mieux coder

Créer du code de qualité grâce aux principes du clean code. Bien coder et avoir un code propre en respectant les bonnes pratiques.

Le Clean Code, c’est l’art d’écrire du code de qualité, de bien coder.

C’est quelque chose que l’on découvre très rapidement lorsque l’on veut progresser en programmation ou simplement devenir meilleur développeur.

La plupart des développeurs n’écrivent pas du code très qualitatif, ce n’est un secret pour personne.

Par manque de formation ou « à cause » d’un budget serré, on se retrouve avec une dette technique incroyable 6 mois après le début du projet.

En tant que développeur, on se doit d’écrire du code de qualité.

C’est pour cela que j’ai décidé de rassembler dans cet article les principales erreurs de développement que je vois en code review.

PS : Ce n’est pas un énième article sur le clean code même si de fait, j’en parle également. C’est surtout un recueil de mon expérience et des mauvaises pratiques que j’ai pu voir en tant que lead developer.

PS2 : Je ne parle pas de SOLID dans cet article car j’en ai fait un billet dédié ici : SOLID – Comprendre les 5 principes en 10 minutes.

Les caractéristiques d’un bon code

Il y a des caractéristiques communes à tout code de qualité.

  • Le code est expressif, ses actions sont claires, tout le monde sait ce qu’il fait
  • Cela semble évident de comprendre comment il fonctionne
  • Il est élégant, on peut dire qu’un bon code est un « beau code »

N’importe qui peut écrire du code qu’un ordinateur comprend. Les bons programmeurs écrivent du code que les humains comprennent.

Martin Fowler

Si on a besoin de relire du code qu’on vient d’écrire pour le comprendre, c’est probablement qu’il est trop complexe.

S’il est complexe pour celui qui l’a écrit, imagine le développeur qui devra reprendre le code derrière !

Dette technique et abandon de qualité

Plus un code est ancien, plus il aura tendance à être modifié par plusieurs autres développeurs.

Plus un code est modifié, plus il aura tendance à contenir des bugs.

C’est ainsi que commence la dette technique.

Le clean code essaye de trouver une solution à cela.

Or ce n'est pas toujours évident, car plus un code est détérioré, moins les développeurs qui passent après auront tendance à se soucier de la qualité de code.

Un exemple simple : la TMA de « vieux » codes dont personne ne veut coder avec.

« Le code est tellement vieux que les versions ne sont plus à jour et il y a tellement de régressions qu’on colle des morceaux de scotch un peu partout. »

Tu vois le truc ?

Et bien plus le code est dégueu, moins tu auras de soucis à rajouter du code dégueu par-dessus.

« De toute façon le code est déjà naze alors… ».

Sache-le, c’est le biais psychologique de « l’hypothèse de la vitre brisée« .

Gérer le code legacy

Un code legacy c’est un vieux code non maintenu / plus vraiment à jour utilisant de vieux outils.

Généralement, c’est le code dont personne ne veut.

Le Clean Code ne fait pas de distinction entre un code lisible avec des bases saines et un autre : on fait de la qualité partout.

La règle que tu dois appliquer :

Laisse le code dans un meilleur état que lorsque tu l’as trouvé.

Uncle Bob Martin

Un petit refacto : splitter une fonction, renommer une variable, créer une nouvelle classe…

Si le code est plus propre après que tu es passé dessus, c’est mission réussie ! 👌

Créer du code propre avec du mauvais code, c’est aussi ça, être un bon développeur.

Refactoring et renommage

Ne pas hésiter à renommer pour rendre plus claires des notions ou des variables.

Chaque petite amélioration est bonne à prendre.

Veille en revanche à ne pas renommer pour renommer, il faut que ça serve un but précis (pour ça, les équipes sont d’une aide précieuse).

Pense aussi que, quitte à renommer une notion, il faut le faire partout (pas seulement dans le fichier sur lequel tu travailles).

Aide-toi de ton IDE pour renommer facilement des aspects métiers.

Objet anémique

Lorsque j’ai découvert que l’encapsulation était un non-sens, ma vie a changé.

Une entité ne doit jamais contenir d’informations invalides.

Tout comme ses fonctions doivent exprimer une intention.

calculate() ne veut rien dire, on calcule quoi déjà ?

getVAT(): int, calculateShippingPrice(): float, ici, on comprend par l’intitulé de la méthode, ce qu’elle fait.

Nommage des classes et des fonctions

Le nommage est très important, bien que ce soit un exercice très difficile de trouver le bon nom à donner à une action.

  • Il faut essayer d’aller à l’essentiel lorsque l’on nomme ses variables
  • Exprimer son type et son intérêt au premier coup d’œil
  • Ne pas avoir peur de faire des noms un peu plus long (HTTP_KO => HTTP_INTERNAL_SERVER_ERROR)
  • N’utilise surtout pas d’acronyme métier (isMOS => isMasterOfService)
  • Évite le « franglais » (shouldBeLisible => shouldBeReadable)
  • Se mettre d’accord sur la traduction pour éviter les synonymes qui disent la même chose (getAllUsers(), fetchAllUsers(), retrieveAllUsers())
  • Utilise des noms métiers dans le code
  • Les mots simples sont à privilégier
  • Code en anglais si possible, les commentaires peuvent être en français si le projet est uniquement français
  • Le nom parfait n’existe PAS (fais juste eu mieux sur le moment donné, le refactoring fera le reste)
  • Éviter les variables d’une lettre (toujours), même dans les boucles : for (let i = 0, i < arr.length; i++) devient for (let index = 0; index < usersArray.length; index++)
  • Éviter de traduire des termes métiers en anglais s’ils n’existent pas / ne sont pas compréhensibles. (VAT est ok pour la TVA, mais Installator ne l’est pas pour Installateur)
  • Utiliser les conventions de nommage du projet en cours, même si elles ne sont pas terribles
  • Les variables d’une lettre ne doivent jamais exister (sauf pour les boucles ou les projections)
  • Nomme les entités de la même manière en Front et en Back (évite d’avoir Employee en front et User en back par exemple, ça perd tout le monde)

Tu peux mesurer la qualité d’un code à sa facilité de compréhension et notamment grâce au nommage..

Type ton code

Le typehint des variables et des fonctions est super important pour la compréhension, la lisibilité générale et l’autocomplétion des IDEs.

Javascript et PHP n’ont pas souhaité typer leur code au début de leur histoire. PHP l’a désormais intégré et Typescript est apparu.

  • Typehint ton code dès que c’est possible
  • Pas besoin de typer ce qui est déjà logique /** @var array */ $users = array();
  • Utilise des énumérations pour typer les entrées / sorties des objets (return 1; => return User.NO_MORE_USER)
  • Sers-toi des abstractions / interfaces quand il y en a besoin, cela évite de dupliquer le code et d’avoir des if / else à rallonge
if vs héritage
Gagner en lisibilité et en qualité de code avec des `extends` ou des `implements`

Le typage n’est pas forcément synonyme de qualité du code, néanmoins cela contribue grandement à limiter le risque d’erreur et à rendre le code intelligible.

Constantes et fichiers de configuration

Tu peux stocker des éléments constants de deux manières différentes.

  • Utilise des constantes quand tu souhaites utiliser l’élément partout dans ton code (Response.INTERNAL_SERVER_ERROR)
  • Utilise un fichier de configuration quand l’action dans ton code va dépendre d’une configuration (webservices.name_of_company.users = 'https://...';)
  • Le fichier de configuration permet également de changer les informations lors d’un changement d’environnement
  • Une constante est globale et immutable pour tout le projet

Évite les trucs trop fancys

La programmation fonctionnelle a permis de récupérer les sorties avec des chaînes, ce qui est très pratique !

Mais si tu en abuses, les développeurs passant après toi ne comprendront pas ton code.

return data.map((d) => [Object.keys(d)[0], Object.values(d)[0]].map(encodeURIComponent).join('=')).join('&');

  • Évite de chaîner les fonctions à outrance
  • Attention à la performance des opérations de chainage (for ... of ... est bien plus rapide que .forEach())
  • Ne pas utiliser le simple égal dans un if ($user = getAllUser()) ... , car confus et ça peut être mal interprété
  • Toujours suivre les coding standards des technos, cela signifie utiliser la Yoda Condition s’il le faut !
  • Globalement ne pas essayer de faire des trucs complexes, même si on adore ça
Condition Yoda en programmation
https://fr.wikipedia.org/wiki/Condition_Yoda

Complexité accidentelle et pré-optimisation

La complexité accidentelle, c’est le fait de coder des choses compliquées en voulant résoudre des problèmes simples.

L’optimisation prématurée est la source de tous les maux (la plupart du temps) en programmation.

Donald Knuth

En voulant optimiser trop vite (créer une nouvelle classe, méthode, abstraction…) on rajoute de la complexité alors qu’elle n’est pas nécessaire.

Attention donc, il faut toujours faire au plus simple (comme en TDD) !

Ne pas oublier que le plus important, c’est la compréhension du code par les équipes.

Pas que le code soit « stylé ».

Bash utilisation du disque en une ligne
https://www.bashoneliners.com/oneliners/312/

Gestion des erreurs

Faut-il remonter l’exception ou utiliser un try / catch ?

C’est toujours compliqué à dire suivant les cas d’utilisation. Voici quelques règles pour que tu puisses y voir plus clair.

  • Ne remonte pas d’exception si tu testes quelque chose dans ta fonction (isUserUnEmployed() doit remonter true ou false)
  • Globalement une fonction pose une question, remonte une exception si elle répond mal à cette question
  • Remonte une exception quand une supposition sur les arguments est erronée (presupposition failure)
  • Les exceptions sont par définition exceptionnelles, évite d’en remonter à chaque fonction
  • Utilise des exceptions quand quelque chose de critique survient ou pour notifier d’un évènement qui ne peut pas être ignoré (si la suite de tes actions dépend d’un appel web service et que celui-ci est injoignable, tu peux remonter un WebServiceNotRespondingException)
  • Crée tes propres exceptions… (InvalidArgumentException => UserCannotBeNullException)

Ce qu’il faut retenir des principes de développement

Les connaître c’est bien, les utiliser, c’est mieux.

Pour ma part je les connais depuis longtemps, mais je ne les applique réellement que depuis quelques années.

Il m'aura fallu créer beaucoup de code pas terrible pour enfin appliquer ces principes...

Comme toute chose, il faut du temps.

SRP (Single Responsibility Principle)

Le principe de responsabilité unique est probablement le plus important.

  • Une fonction n’a qu’une seule action, un seul objectif
  • Elle ne sort pas du scope de son nom, elle fait exactement ce qu’elle dit
  • C’est tout

Il y aurait beaucoup à dire, mais simplement essaye de minimiser les actions de tes fonctions.

KIS (Keep It Simple)

Tout ce qui est compliqué n’a jamais produit de bons résultats.

Louis-Napoléon Bonaparte, Les œuvres de Napoléon III (1854-1869)
  • La solution la plus simple est toujours la meilleure
  • Dès que c’est compliqué essaye de découper au maximum
  • Plus c’est petit, plus c’est facile à comprendre
  • Découpe ton code en fonctionnalités (UserService => UserCreatorService, UserBankingAuthenticatorService)

DRY (Don’t Repeat Yourself)

Ça a l’air simple à première vue, mais la limite est parfois obscure…

Imaginons que tu aies deux tableaux. Ces tableaux affichent des informations différentes, mais sont très similaires par leur style et leur template.

Tu pourrais te dire, je vais utiliser une interface pour afficher les objets et gérer les petites différences de style avec des conditions.

À un moment donné, tu te retrouveras avec des if (type === Type.User) et des if (type === Type.Group) dans ton code ; pas vrai ?

À partir de combien de chaque il vaudrait mieux faire deux fichiers séparés ?

Globalement tu dois autant que possible :

  • Partager au maximum le code similaire
  • Dès que tu veux copier/coller du code, extrais-le dans une fonction d’un service
  • N’aie pas peur de découper ton code, au contraire tu le rends modulable

On a le droit de dupliquer du code !

Le Clean Code ne dit pas que toutes les duplications sont à bannir.

Le fait de ne pas se répéter implique du coupable.

Plutôt que de copier/coller le code de A dans B, on va déléguer ça à C et relier A et B (indépendamment) à C.

// Plutôt que de répéter ce morceau de code (et de le copier-coller)

// Code A
if (user.roles.includes("admin")) {
    return false;
}

// Code B
if (user.roles.includes("admin")) 
    return false;
}

// On préferera utiliser une fonction pour ça 
function checkUserRightFor(userToCheck: User, role: string): void {
    // Code C
    if (userToCheck.roles.includes("admin")) {
        throw new Exception(`User ${userToCheck.name} does not have role ${role}`);
    }
}

// et l'appeler partout où il y en a besoin (dans A et B)
checkUserRightFor('admin');

Si on modifie C (la fonction couplée), alors cela impactera A et B.

Quelques règles pour éviter une mauvaise duplication :

  • On ne duplique pas les règles métiers
  • Si le code est dans un autre domaine, peut-être aura-t-il tendance à diverger avec les temps

YAGNI (You Aren’t Gonna Need It)

Moins connu que les autres, mais tellement d’actualité aujourd’hui.

« Tu n’en auras pas besoin ».

À l'heure où les frameworks JS sont utilisés à tout va, on peut supposer que tous les projets n'ont pas besoin de la stack React + Redux + Socket.io + ...

Quel est le besoin de ton client ? Une landing page avec un formulaire tout simple ?

HTML et CSS devraient suffire, PHP si c’est contribuable ou un autre langage back.

L’idée principale c’est de ne pas avoir 20.000 dépendances.

Typiquement on peut prendre l’exemple de jQuery qui n’est je pense plus vraiment utile dans la plupart des cas.

Pourtant je vois tellement de projets encore avec… C’est toujours une des libs JS les plus populaires en 2020.

Et pourtant, on n’en a pas du tout besoin la plupart du temps.

SOC (Separation of Concerns)

« Faire une seule chose, mais la faire bien ».

C’est toujours mieux que de tout faire et mal ! Contrairement à ce qu’on pense nous les devs, tout bien faire est souvent une chimère.

« Do One Thing And Do It Well. »

La philosophie d’Unix.

Sépare tes fonctionnalités en lot. Dans ces lots crée des parties…

Fais des petites choses, mais fais-les bien.

Cela signifie aussi de créer des fonctionnalités testées et séparées.

ACID (Atomicité, Cohérence, Isolation et Durabilité)

Le principe ACID permet de s’assurer qu’une action a bien été réalisée et que son action soit ce que l’on attendait d’elle.

Ce qu’il faut retenir c’est de ne jamais renvoyer OK si on n’est pas certain l’action a bel et bien été réalisée.

  • Atomicité : 1 ou 0, soit la transaction a été complètement réalisée, soit pas du tout.
    Comme exemple, on peut citer la création d’un utilisateur en base de données. Rendre sa création atomique, c’est s’assurer que l’objet ET tous ses champs ont bien été persistés.
  • Cohérence : Aucune action ne doit rendre les données corrompues ou le système inopérant.
    On crée par exemple des contraintes d’intégrités ou des cascades pour ne pas avoir de relations orphelines du côté de la base de données
  • Isolation : Chaque transaction est isolée et ne dépend pas d’une autre.
    Exemple : Tu peux lancer deux requêtes sur une route api POST /api/users, aucune de ces deux requêtes ne doit dépendre de l’autre.
  • Durabilité : Ne pas renvoyer de code retour correct si on n’est pas certain que l’action a bien été menée à son terme.
    Sur une création d’un utilisateur en base de données, on retourne souvent l’utilisateur nouvellement créé avec son ID comme preuve que ce dernier est bien enregistré.

IRI (Intention Revealing Interfaces)

J’ai découvert cette notion non pas par le Clean Code, mais pas le DDD (Domain-Driven Design).

Le principe est simple : Le code doit révéler notre intention, l’idée métier derrière doit être simple, concrète et lisible.

4 règles pour un design simple

Ce sont 4 règles données par Kent Beck (l’auteur du célèbre « Test-Driven Development by Example » (TDD par l’exemple).

  1. Le code doit passer les tests (fonctionner comme prévu)
  2. Révéler l’intention (en le lisant, tu sais de quoi il parle) – Intention Revealing Interfaces
  3. Éviter la duplication (les règles métiers à un seul endroit) – Dry, Don't Repeat Yourself
  4. Coder « petit », traite les choses le plus finement possible sans chercher à en faire trop (quand c’est trop gros, on rajoute de la complexité)

Linters et coding standards

Les linters te permettent d’avoir un warning dans ton IDE quand ton code peut être amélioré.

Il permet notamment :

  • De détecter les erreurs dans le code source
  • De corriger les problèmes de syntaxe
  • Le respect les bonnes pratiques de ton langage
  • La mise en place des coding standards

Mieux encore, la plupart des linters viennent avec un fixer, tu peux corriger ces erreurs simplement avec un clic droit…

Les linters rendront ton code bien plus lisible.

  • Prends le temps d’installer les linters suivants ton projet (eslint, sass-lint, php-coder-sniffer, wpcs, jslint…)
  • Utilise les conventions données par ton framework (les bonnes pratiques WordPress sont différentes de celles de Symfony)
  • Respecte le « coding style » de chaque langage (comme la PSR-12 pour PHP)
  • Apprends les design patterns les plus utilisés (Singleton, DTO, Adapter etc)

Nettoyage du code

En théorie le clean code stipule que tu es censé laisser le code plus propre en partant qu’il l’était en arrivant.

Cependant la complexité peut rendre la refactorisation du code difficile, attention aux régressions.

La qualité globale du code du projet est à prendre en compte lorsque l’on est développeur, pas seulement la qualité de SON code.

  • Supprime déjà le code commenté et autres « console.log » quand tu en vois
  • Fais attention aux boucles imbriquées, ça peut vite tourner au vinaigre
  • N’abuse pas des ternaires $variable == $x ? ($x == $y ? array_merge($x, $y, $z) : $x) : $y;
  • Si ton for a un break, c’est sûrement un while
  • Rend les choses logiques et lisibles après ton passage
  • Commente les éléments que tu as eu du mal à comprendre
  • « Quand y’a un doute, y’a pas de doute ». Si jamais tu « penses » que ce code doit être modifié, c’est sûrement qu’il doit l’être
for vs while
Si ton for a un `break`, c’est que ça devrait plutôt être un while.

Comment bien commenter son code

C’est toujours un sujet de débat dans la communauté, certains disent que tout doit être commenté, d’autres disent que rien ne doit l’être.

Est-ce que celui-ci doit l’être ?

/**
* Returns a customer based on its id.
* 
* @param int $id
* @return Customer
*/
public function getCustomerById(int $id): Customer;

En tout cas un code propre passe par l’utilisation des commentaires.

Essayons de prendre le meilleur des deux mondes et de rester pragmatique.

  • Tu n’es pas obligé de commenter la déclaration si ce qu’elle dit est évident /** The day of the month. */ primate $dayOfMonth;
  • Commente quand ça devient compliqué ou que tu en ressens le besoin, le développeur suivant te remerciera
  • Attention aux TODOs, ils restent à vie dans le code…
  • Exprime tes intentions via tes commentaires (j’ai dû faire ça, parce que ceci…)

Les risques des commentaires dans le code

Si un commentaire sert à expliquer quelque chose de technique, demande-toi :

Est-ce que ces commentaires sont nécessaires ?

Si oui, du coup, ne penses-tu pas que le code est trop complexe s’il a besoin de commentaire ?

Les commentaires ne doivent pas servir à expliquer ce que le code fait.

Mais plutôt à expliquer une notion métier complexe (mais simple dans le code) ou à apporter des idées supplémentaires.

Le pire, c’est que souvent on met à jour le code, mais pas la documentation (sous forme de commentaire).

// The Person class is used as a male or a female, or even an IA ✅

public class Person {

    // private String name; <-- code commenté. à bannir ❌

    private int age; // the age of the person ❌

    private String text; // the description of the person's job ❌

    public boolean isAboveTheAge() {
        // @TODO make this a constante 🔸 <-- va sans doute rester dans le code toute la vie, mais c'est mieux que rien
        return (this.age >= 18);
    }
}

Découpage et longueur

Plus une méthode est unique, plus elle est facile à lire, plus elle sera facile à déboguer.

Elle sera même encore plus performante.

  • N’hésite pas à créer beaucoup de fichiers, c’est comme ça que l’on découpe
  • Encore une fois, essaye de faire en sorte qu’un fichier ait un scope bien précis (UserBasketService ne s’occupe que des opérations dans le panier, UserPaymentService s’occupe uniquement paiement sur la plateforme…)
  • UserService ne veut rien dire, personne sait ce qu’il y a de dedans, tout comme UserManager, UserInfo, UserController, UserData
  • 300 lignes pour un fichier, ça commence à être beaucoup
  • 140 caractères maximum de largeur, y compris pour le HTML (le scroll horizontal nuit à la lisibilité et à la compréhension de ton code)
  • 30 lignes pour une fonction c’est déjà pas mal
  • Jamais plus de 3 niveaux d’indentation par méthode (utilise des fonctions privées, splite le code)
  • À partir de 5 arguments (ou paramètres), essaye de découper le traitement dans ta fonction ou passe plutôt un objet
  • 1 fonction ne fait qu’1 seule chose
  • Rendre les paramètres d’une fonction immutables (non changeables / variables) est une bonne chose
  • Si la valeur retournée d’une fonction est complexe, on peut l’encapsuler dans un type dédié
  • Penser petit : Généralement, plus on découpe, mieux c’est.
  • « Aucune fonction ne devrait être définie plus haut que le code qui l’appelle »

Avoir un code de qualité passe également par la longueur des différents fichiers.

Ces chiffres sont des indications pour tendre vers un code plus lisible, ils ne sont pas absolus.

Conclusion

Faire du code de qualité est quelque chose de difficile, on a tous notre notion de « code qualitatif ».

Pour écrire un code source de qualité, tu dois écrire du code lisible, propre, compréhensible de tous.

J’aurais voulu parler de l’utilisation des tests dans cet article.

« Mais tester, c’est douter. »

En vérité je n’en ai pas assez mis en place pour pouvoir en parler librement.

Néanmoins les tests sont de très bons outils que tu dois essayer d’utiliser pour rendre ton code encore plus sûr.

D’ailleurs en parlant d’outils, coder sur papier pourrait t’aider à mieux coder !

J’espère que cet article t’aura donné des pistes pour mieux programmer.

Si tu cherches à en savoir davantage sur le clean code, consulte cet article.

Si jamais tu vois des éléments à rajouter à cette liste, n’hésite pas à laisser un commentaire.

Plus de contenu 💡

Pour lire plus de contenu similaire dans le même thématique.

6 commentaires

  1. Avatar de Maxime

    Bonjour,

    Merci pour cet article fort intéressant.

    Peux tu stp expliquer ce point :
    => « Sers-toi des interfaces quand il y en a besoin, cela évite de dupliquer le code et d’avoir des if / else à rallonge »

    Et donner un exemple pour celui-là :
    => Si ton for a un break , c’est sûrement un while

    Merci d’avance

    • Avatar de Alex

      Hello Maxime,

      Avec un peu de retard, je viens de mettre à jour l’article avec deux captures d’écrans !

      N’hésite pas si tu as d’autres questions.

      Alex

  2. Avatar de BrCris

    Cet article apporte et renforcer mes connaissances du développement d’applications.

    Je reconnais que ce n’est pas facile d’accepter de quitter de foreach vers while.

    Vous avez raison de dire « faire du code de qualité est quelque chose de difficile, on a tous notre notion de « code qualitatif » « .

  3. Avatar de Mohammad Trabelsi

    Merci pour la qualité de l’article.
    Je pense qu’il faut détailler plus les mesures quantitatives de qualité de code et donner des exemples génériques pour garder un niveau d’abstraction qui marche sur la majorité des languages de programmation.

    • Avatar de Alex so yes

      Merci pour le commentaire !

      Tu as raison, mais c’est difficile de trouver des sujets globaux qui vont à tous les développeurs. J’espère avoir réussi un peu quand même 🙂

👩‍💻 Réagir à cet article 👨‍💻

Merci de partager ton histoire avec la communauté !