Blog de gJmry
🇫🇷 / 🇬🇧

Pourquoi je ne suis pas fan des commentaires

J’accorde une grande importance à la qualité du code. À mon sens, prendre le temps de revoir et d’améliorer son code est essentiel. Cela permet non seulement de rendre les choses plus claires pour soi-même, mais aussi d’éviter de passer 28 jours à déboguer un problème à l’avenir, que ce soit pour vous ou pour un autre développeur qui viendra intervenir sur votre projet.

Les principes de bases


Source : https://blog.mocoso.co.uk/posts/talks/take-control-of-code-quality/

Quand on veut écrire un code de qualité, il y a certains principes fondamentaux à suivre. Ce sont des mantras simples qu’on doit répéter constamment pour garantir un code cohérent et fiable. Parfois, ces principes demandent du temps à assimiler, surtout si l’on débute, mais avec de la pratique, ils deviennent naturels et s’intègrent facilement dans notre façon de coder.

Clarté du code


Ce principe est souvent le premier auquel on pense, car il est évident pour tout le monde : un code doit être clair, surtout lorsqu’on doit le relire plus tard. Il est essentiel de ne pas tomber dans les pièges de boucles inutiles, de noms de variables incohérents, ou de fonctions avec des paramètres mal nommés qui rendent le code difficile à comprendre pour quelqu’un d’autre (ou même pour soi dans quelques mois).

Code peu clair et difficile à comprendre


function calc(a, b) {
    let s = 0;
    for (let i = 0; i < a; i++) {
        for (let j = 0; j < b; j++) {
            s += i * j;
        }
    }
    return s;
}

Ici, on ne comprend pas vraiment ce que la fonction fait, on ne comprend pas son but. Les noms des paramètres a et b sont trop génériques, et la variable s ne donne aucune indication sur ce qu’elle contient. Le code manque de contexte, rendant son utilisation et son objectif flous. Si quelqu’un doit déboguer ce code plus tard, il sera obligé de passer par plusieurs étapes pour comprendre ce qu’il fait réellement, ce qui constitue une perte de temps considérable.

⚠️ Rappel : il s’agit ici de clarté du code, pas de la logique. Même si le code est fonctionnel, sa lisibilité est primordiale.

Code clair et bien structuré

function calculerSommeTotale(nombreDeProduits, prixParProduit) {
    let sommeTotale = 0;
    for (let i = 0; i < nombreDeProduits; i++) {
        sommeTotale += prixParProduit;
    }
    return sommeTotale;
}

Dans cet exemple, le nom de la fonction calculerSommeTotale indique clairement ce que fait la fonction. Les paramètres nombreDeProduits et prixParProduit sont explicites et montrent leur rôle dans le calcul. La variable sommeTotale reflète bien l’objectif de la fonction, ce qui rend le code beaucoup plus facile à comprendre à première vue. Ce type de clarté permet de gagner du temps lors de la lecture ou du débogage et assure une meilleure maintenance du code à long terme.

Simplicité


La simplicité est un principe fondamental dans le développement de code de qualité. Elle repose sur l’idée qu’un code doit être aussi simple que possible pour accomplir sa tâche, sans ajout de complexité inutile. Le principe de la simplicité est souvent résumé par l’acronyme KISS, qui signifie “Keep It Simple, Stupid” (en français : “Garde ça simple, imbécile !”).

Suivre le principe KISS, c’est éviter de créer des constructions complexes ou de chercher des solutions sophistiquées lorsque quelque chose de plus simple ferait le travail. Un code simple est plus facile à comprendre, à maintenir, et il est moins susceptible d’introduire des bugs. Il est également plus rapide à tester et à modifier.

Un exemple classique de ce principe est la façon de gérer des conditions répétitives dans le code. Au lieu de créer des solutions trop alambiquées, il vaut mieux chercher une approche plus directe et intuitive.

Exemple de code complexe (contre le principe KISS) :

function obtenirPrixRemise(prix, remise) {
    if (prix < 0) {
        throw new Error("Le prix ne peut pas être négatif");
    }
    if (remise < 0 || remise > 100) {
        throw new Error("La remise doit être comprise entre 0 et 100");
    }

    let prixRemise = prix;

    if (remise >= 0 && remise <= 10) {
        prixRemise = prix - (prix * 0.1);
    } else if (remise > 10 && remise <= 20) {
        prixRemise = prix - (prix * 0.15);
    } else if (remise > 20 && remise <= 30) {
        prixRemise = prix - (prix * 0.2);
    } else if (remise > 30 && remise <= 40) {
        prixRemise = prix - (prix * 0.25);
    } else if (remise > 40 && remise <= 50) {
        prixRemise = prix - (prix * 0.3);
    } else {
        prixRemise = prix - (prix * 0.5);
    }

    return prixRemise;
}

Dans cet exemple, la logique de réduction peut être considérée comme trop complexe, avec une série de conditions imbriquées pour gérer chaque tranche de réduction. Bien sûr cela fonctionne, mais ce n’est pas ce que l’on souhaite, on veut un code simple.

Exemple de code simple (respectant le principe KISS) :

function obtenirPrixRemise(prix, remise) {
    if (prix < 0 || remise < 0 || remise > 100) {
        throw new Error("Prix ou remise invalides");
    }
    return prix * (1 - remise / 100);
}

En dehors de l’aspect débile de la première logique, celle-ci est beaucoup plus simple et directe. On applique directement le pourcentage de réduction à la variable price, ce qui évite une série de conditions et rend le code beaucoup plus lisible et facile à maintenir. Le principe KISS permet ainsi d’atteindre l’objectif avec beaucoup moins de code, ce qui est plus efficace et plus facile à comprendre.

Modularité


Maintenant qu’on a prit du temps à faire notre code propre, ça serait idiot de devoir le réécrire pour faire la même chose dans une autre partie. C’est pourquoi il est important de coder par modules

graph TD; A[Application de Calcul] --> E[Calculs] A --> F[Enregistrement] A --> G[Affichage]

Dans cet exemple, on voit un code mal structuré. Tout est centralisé dans l’application principale sans aucune séparation. Si l’on souhaite ajouter une nouvelle fonctionnalité, cela devient rapidement complexe, car il est difficile de réutiliser un module ou une fonction existante. Chaque modification devra potentiellement impacter plusieurs parties du code, ce qui rend l’entretien du projet ardu.

graph TD; A[Application de Calcul] --> B[Module Calculatrice] A --> C[Module Historique] B --> D[Addition] B --> E[Soustraction] B --> F[Multiplication] B --> G[Division] C --> H[Enregistrement] C --> I[Affichage]

Ici, l’application est structurée de manière modulaire, avec des sous-modules bien définis et indépendants. Chaque module est responsable d’une fonctionnalité précise, ce qui permet une meilleure organisation du code.

Par exemple, si je veux ajouter un module “Carré” à la calculatrice, je pourrais facilement réutiliser le code du module Multiplication pour implémenter cette fonctionnalité. Cette modularité permet non seulement une réutilisation efficace du code, mais aussi une meilleure extensibilité et maintenabilité de l’application.

Mais les commentaires ?


C’est vrai, c’est le sujet du blog, et on pourrait vite s’y perdre à force de discuter d’organisation du code. Mais je vais enfin aborder mon point : dans les trois principes que nous avons vus, nous avons systématiquement vu du code à écrire. Une fois ce code écrit, il ne changera que rarement, car après plusieurs refactorings, il atteindra peut-être un stade de “perfection” (même si, soyons honnêtes, cela n’arrive jamais, mais on peut rêver). Le principe du commentaire

Un commentaire a pour objectif d’apporter des précisions à un code existant. Typiquement, lorsque l’on reprend un ancien code, les commentaires peuvent aider à l’expliquer et le rendre plus compréhensible. Par exemple, si l’on prend ce code ci-dessous, on pourrait utiliser des commentaires pour rendre l’intention plus claire.

function calc(a, b) {
    let s = 0; // Initialisation de la somme à zéro
    for (let i = 0; i < a; i++) { // Parcours de a éléments
        for (let j = 0; j < b; j++) { // Pour chaque élément de b
            s += i * j; // Ajout du produit i * j à la somme
        }
    }
    return s; // Retourne la somme totale
}

Maintenant à quoi bon utiliser les commentaires, si je produis du bon code par défaut ?

function calculerSommeTotale(nombreDeProduits, prixParProduit) {
    let sommeTotale = 0; // Initialisation de la somme totale à zéro
    for (let i = 0; i < nombreDeProduits; i++) { // Parcours des produits
        sommeTotale += prixParProduit; // Ajout du prix de chaque produit à la somme totale
    }
    return sommeTotale; // Retourne la somme totale des produits
}

Et bien on obtient un code surchargé qui affiche plus d’informations que nécessaires. Entre autre on oublie complétement le deuxième principe de code qu’on avait (Principe KISS). Notre superbe code est passé de bon à surchargé, tout ça en voulant bien faire.

Son utilisation au fil du temps


Auparavant le commentaire avait un aspect de précision, son but était d’indiquer au développeur ce que faisait le code, comment, et pourquoi. Lorsque les équipes étaient désorganisées, les variables mal nommées, un bout de commentaire était un peu le super-héro qu’on attendait pour comprendre le code à la place de se casser le crâne.

La documentation

Une utilisation répandu était d’utiliser les commentaires pour créer de la documentation au sein du code. Un exemple typique de ce phénomène est la JavaDoc.

/**
* Enregistre le texte à afficher dans une info-bulle. Le texte
* s'affiche lorsque le curseur reste immobile sur le composant.
*
* @param text  la chaîne de caractères à afficher. Si le texte est nul,
*              l'info-bulle est désactivée pour ce composant.
*/
public void mettreBulleAide(String text) {
    /// Reste du code
}

Donner du contexte

Parfois, le commentaire est utile pour fournir un contexte supplémentaire, en particulier lorsque le code est complexe ou a une logique spécifique qui n’est pas évidente au premier abord. Cela peut être lié à une décision de conception, à une limitation particulière ou à une astuce technique.

Exemple 1 : Un algorithme complexe

// Utilisation d'un algorithme de tri par insertion pour optimiser la recherche
// de la valeur maximale dans une liste triée de façon ascendante
for (int i = 1;i < list.length; i++)
{
    int
    key = list[i];
    int
    j = i - 1;
    while (j >= 0 && list[j] > key) {
        list[j + 1] = list[j];
        j = j - 1;
    }
    list[j + 1] = key;
}

Ici, le commentaire explique l’algorithme et justifie son utilisation dans un contexte précis, ce qui permet à un développeur de comprendre la logique derrière l’implémentation sans avoir à retracer l’algorithme par lui-même.

Exemple 2 : Justification d’un choix

// Utilisation de la méthode 'equals' plutôt que '==' pour comparer des objets
// afin de s'assurer que les deux objets ont la même valeur et non la même référence
if (objet1.equals(objet2)) {
    // Code
}

Dans cet exemple, le commentaire clarifie la raison pour laquelle l’égalité des objets est comparée à l’aide de la méthode equals et non de l’opérateur ==. Cela peut éviter des erreurs de compréhension ou d’implémentation dans le futur.

Coder sa documentation


Citation récurrente contre les commentaires

Cette citation résume parfaitement ma pensée sur les commentaires : si le code est suffisamment clair et explicite, les commentaires deviennent superflus. Si nous devons justifier un choix, cela doit être fait à travers le code lui-même. Si une fonction ou une variable nécessite une explication supplémentaire, il suffit de l’exprimer clairement dans le code.

Comment faire ?


Il faut écrire du bon code, du code de qualité, qui s’exprime par lui-même et n’a pas besoin d’une intervention externe pour être compris. Même si cela peut sembler être une tâche supplémentaire au début, vous économiserez à long terme le temps que vous auriez passé à écrire des commentaires, ainsi que celui de la personne qui lira votre code par la suite.

Les bonnes pratiques


Pour se mettre à coder sans commentaires, le meilleur moyen est de maîtriser les bases évoquées précédemment et de se concentrer sur la qualité du code. L’effort initial peut sembler long, mais les bénéfices à long terme sont considérables. Un code clair, bien structuré et auto-explicatif permet de gagner un temps précieux, et cela sera rapidement apprécié par vos collègues.

Mes sources


Je ne sors pas ça de nulle part, même si j’aimerai bien réfléchir aussi bien seul. En dehors de mon expérience, le livre Coder Proprement de Robert C. Martin m’a beaucoup aidé lors de la rédaction de ce blog, je vous le recommande chaudement si la qualité de développement vous intéresse.


// Ne prenez pas mon avis pour une vérité, je ne suis qu’un simple développeur qui voulait partager son avis sur une partie du développement qu’il apprécie