Temps de lecture estimé : 20 minutes
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.
Temps de lecture estimé : 20 minutes
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.
Il y a des caractéristiques communes à tout code de qualité.
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 !
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« .
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.
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.
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.
Le nommage est très important, bien que ce soit un exercice très difficile de trouver le bon nom à donner à une action.
HTTP_KO
=> HTTP_INTERNAL_SERVER_ERROR
)isMOS
=> isMasterOfService
)shouldBeLisible
=> shouldBeReadable
)getAllUsers()
, fetchAllUsers()
, retrieveAllUsers()
)refactoring
fera le reste)for (let i = 0, i < arr.length; i++)
devient for (let index = 0; index < usersArray.length; index++)
VAT
est ok pour la TVA
, mais Installator
ne l’est pas pour Installateur
)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..
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.
/** @var array */ $users = array();
return 1;
=> return User.NO_MORE_USER
)if
/ else
à rallongeLe 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.
Tu peux stocker des éléments constants de deux manières différentes.
Response.INTERNAL_SERVER_ERROR
)webservices.name_of_company.users = 'https://...';
)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('&');
for ... of ...
est bien plus rapide que .forEach()
)if ($user = getAllUser()) ...
, car confus et ça peut être mal interprété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é ».
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.
isUserUnEmployed()
doit remonter true
ou false
)WebServiceNotRespondingException
)InvalidArgumentException
=> UserCannotBeNullException
)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.
Le principe de responsabilité unique est probablement le plus important.
Il y aurait beaucoup à dire, mais simplement essaye de minimiser les actions de tes fonctions.
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)
UserService
=> UserCreatorService
, UserBankingAuthenticatorService
)Ç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 desif (type === Type.User)
et desif (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 :
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 :
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.
« 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.
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.
route api POST /api/users
, aucune de ces deux requêtes ne doit dépendre de l’autre.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.
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).
Intention Revealing Interfaces
Dry, Don't Repeat Yourself
Les linters te permettent d’avoir un warning dans ton IDE quand ton code peut être amélioré.
Il permet notamment :
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.
eslint
, sass-lint
, php-coder-sniffer
, wpcs
, jslint
…)WordPress
sont différentes de celles de Symfony
)Singleton
, DTO
, Adapter
etc)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.
console.log
» quand tu en vois$variable == $x ? ($x == $y ? array_merge($x, $y, $z) : $x) : $y;
for
a un break
, c’est sûrement un while
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.
/** The day of the month. */ primate $dayOfMonth;
TODOs
, ils restent à vie 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);
}
}
Plus une méthode est unique, plus elle est facile à lire, plus elle sera facile à déboguer.
Elle sera même encore plus performante.
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
…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.
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.
Pour lire plus de contenu similaire dans le même thématique.
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
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
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 » « .
En effet… Bon courage pour ton blog !
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.
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 🙂