Laisser un commentaire

Bien commenter son code

Commenter son code, c’est un cadeau que vous envoyez dans le futur. Peut-être pour le prochain développeur qui maintiendra le projet ou vous donnera un coup de main, mais probablement surtout pour votre futur vous.

En effet, ce qui est limpide dans votre esprit à l’instant où vous écrivez le code, sera clairement plus opaque lorsque quelques semaines ou mois se seront écoulés. La documentation – en plus d’un code bien structuré – participe activement à faire de votre base de code dans son ensemble un “bon code”.

qu'est-ce que le bon code en BD
Chacun a sa propre définition du bon code, mais un bon code est toujours clair

Les commentaires inline

Il ne s’agit pas d’expliquer ligne par ligne ce que fait le code, loin de là. Si un petit commentaire inline s’avère parfois utile, le code ne doit pas en être truffé.

Ce type de commentaire permet d’expliquer au plus près du code une particularité de ce dernier, la raison d’être d’un détail ou le pourquoi de l’emploi d’une méthode qui ne semble par forcement évidente.

function humanDate(date, locales) {
    …

    const dateYear = dateObj.toLocaleString(locales, { year: 'numeric' });
    const dateMonth = dateObj.toLocaleString(locales, { month: 'numeric' });
    const dateDay = dateObj.toLocaleString(locales, { day: 'numeric' });

    const now = new Date();
    const nowYear = now.toLocaleString(locales, { year: 'numeric' });
    const nowMonth = now.toLocaleString(locales, { month: 'numeric' });
    const nowDay = now.toLocaleString(locales, { day: 'numeric' });

    …

    // set year only if not the same year as now
    if (dateYear !== nowYear) options.year = 'numeric';

    // if today, display relative time
    if (dateYear === nowYear && dateMonth === nowMonth && dateDay === nowDay) {
        …
    }

    …
}

Ici, on comprend tout de suite l’intérêt des commentaires inline, ils précisent de manière univoque le rôle des if, sans qu’il faille méthodiquement se plonger dedans à la lecture pour comprendre ce que font ces conditions.

Function level, c’est là que ça se passe

Les commentaires inline permettent d’apporter du contexte, mais c’est bien les commentaires au niveau des fonctions qui représentent la réelle plue-value des commentaires.

Ils constituent une documentation en eux-même. De nombreux langage ont leur standard, en JavaScript, il s’agit de JSDoc.

/**
 * Send custom request using fetch api
 * @param { String } url
 * @param { String } method
 * @param { Object } body
 * @return { Promise }
 */
function ajax(url, method, body) {
    …
}

Grâce à ça, il n’est la plupart du temps même pas utile de se pencher dans la fonction elle-même, à la simple lecture du commentaire, on sait tout de la fonction :

Le JSDoc est composé d’un bloc, commençant par /**, ensuite chaque ligne commence par un * et enfin, le bloc est fermé comme n’importe quel commentaire multi-lignes.

Ce format permettra à votre éditeur d’immédiatement reconnaître le JSDoc et de lui appliquer la coloration syntaxique. Malheureusement, highlight.js, que j’utilise sur ce site, échoue dans cette mission.

Expliquer ce que fait la fonction

La première chose que fait un commentaire JSDoc est d’expliquer en langage humain ce que fait la fonction.

Bien qu’aucun format ne soit imposé, on tente tout de même de rester concis. Cependant, si cela est nécéssaire, l’explication peut être écrite sur plusieurs lignes. C’est bien plus propre et lisible que d’avoir une seule ligne de 10Km de long !

Enfin, c’est une question de goût, mais je m’astreins à ce que cette phrase commence toujours par une majuscule, cela indique qu’il s’agit d’une vraie phrase construite avec une grammaire valide. Je ne l’utilise pas mais il existe même une règle ESLInt pour ça.

Les paramètres

On entre ici dans le cœur de la documentation : quels sont les paramètres que la fonction prend en entrée ?

Ce sujet est plus épineux qu’il ne paraît. En effet, un paramètre possède un type contraint ou peut en accepter plusieurs, il peut être requis ou optionnel, avoir une valeur par défaut, accepter certaines valeurs… Voyons comment exposer ces contraintes de manière claire.

Pour spécifier qu’on documente un paramètre, on commence toujours la ligne par @param. Ensuite, entre accolades, le type du paramètre, puis son nom et, optionnellement, une description. Notez d’ailleurs que cette phrase peut être séparée du nom du paramètre par un tiret.

De nouveau, j’ai pour convention de laisser un espace entre le type du paramètre et les accolade (comme dans les objets JavaScript) et de mettre la première lettre du type en majuscule. Rien ne vous y oblige, mais je trouve cela plus clean.

Par ailleurs, la liste des types n’est pas restreinte aux types natifs JavaScript. Si vous souhaitez dire que le paramètre est un entier, vous pouvez parfaitement mettre Integer car c’est plus précis que le simple type natif Number.

Cela dit, vous pouvez également le préciser en commentaire.

* @param { Number } age Age of the user as an integer

Lorsque je documente des fonctions en front, lesquelles reçoivent des éléments du DOM, je trouve qu’il est plus clair d’indiquer directement de quel type d’élément il s’agit plutôt que de vaguement indiquer qu’il s’agit d’un objet.

Vous pouvez à ce titre regarder comment je documente les fonctions dans ƒlightDom, dont l’exemple ci-dessous est tiré.

/**
 * Toggle a class on an element
 * @param { HTMLElement } el
 * @param { String } className
 */
export function toggleClass(el, className) {
    if (hasClass(el, className)) removeClass(el, className);
    else addClass(el, className);
}

Multi-types

C’est un cas qui arrive, un paramètre peut recevoir des valeurs d’un type ou d’un autre, par exemple un nombre ou une chaîne de caractère (laquelle peut représenter un nombre).

/**
 * Send an email via SMTP
 * @param { (String | Array) } to
 * @param { String } text
 * @param { String } subject
 * @return { Promise }
 */
function sendMailWithSMTP(to, subject, text) {
    …
}

Il est possible qu’on ne puisse connaître à l’avance le type ou que tous les types soient acceptés, dans ce cas, comme dans les REGEX, on utilise le symbole *.

* @param { * } response Response of the previous function call

Dans le cas où le paramètre accepte un type donné ou null, on le signifie à l’aide d’un ?.

* @param { ?Integer } age

Paramètre optionnel et par défaut

Si le paramètre est optionnel, on l’entoure de crochets.

/**
 * Send client err if it's validation error or log if it's a native or low level error
 * @param { Object } err error object
 * @param { Object } [res]
 */
function handleError(err, res) {
    …
}

Dans cette fonction, il est clair que la paramètre res n’est pas obligatoire. Il existe également des cas où, sans passage du paramètre, on lui assigne une valeur par défaut.

/**
 * Signup a new user
 * @param { String } name
 * @param { String } password
 * @param { Integer } age
 * @param { String } [gender=male]
 */
function createUser(name, password, age, gender = male) {
    …
}

Il est à noter qu’il n’est pas obligatoire que le paramètre ayant une valeur par défaut soit optionnel, c’est cependant souvent le cas, question de logique.

Type valide et…

L’autre question à laquelle on doit souvent faire face est la restriction du paramètre, voyons quelques exemples.

// la valeur doit être une des options de la liste
* @param { String="male, female" } gender

// limiter le nombre de caractères d'un string
* @param { String{..25} } password

// ou l'encadrer entre 10 et 25 par exemple
* @param { String{10..25} } password

La logique sera la même pour un nombre, mais cela précisera sa valeur, pas sa longueur. Un bon exemple concerne les codes postaux français.

* @param { Integer{10000-99999} } zipcode

Documenter les objets

Lorsque l’on passe un objet en paramètre, il s’avère souvent utile de préciser son contenu. La logique reste la même que ce que nous avons vu jusqu’à maintenant.

/**
 * Signup a new user
 * @param { Object } user
 * @param { String } user.name
 * @param { String } user.password
 * @param { Integer } user.age
 * @param { String } user.gender
 */
function createUser(user) {
    …
}

Il existe une autre forme plus compacte, notamment utile lorsque l’on veut succinctement documenter un return.

* @param { Object.<name: String, age: Integer> }

Documenter les tableaux

La documentation des tableaux est assez proche de celle des objets. On déclare qu’il s’agit d’un tableau puis les types qu’il contient.

/**
 * Compute student's mean grade
 * @param { Object[] } students
 * @param { String } students[].name
 * @param { Number } students[].mathMean
 * @param { Number } students[].frenchMean
 */
function computeMean(students) {
    …
}

De même que pour les objets, il existe une notation plus compacte,

// dans le cas où le tableau contient un unique champ
* @param { Array.<String> } studentsNames

// dans le cas où il est composé d'objets
* @param { Array.<Object> } studentsNames

// on peut aussi spécifier le contenu de l'objet
* @param { Array.<{ name: String, age: Integer }> } studentsNames

Documenter les fonctions

N’est-ce pas déjà ce dont nous sommes en train de parler

C’est probablement ce que vous vous demandez. Cependant, il s’agit ici des fonctions que nous passons en argument. Comme vous le savez, en JavaScript, les fonctions sont des citoyens de premier rang, les fonctions peuvent donc accepter des fonctions en paramètres et retourner d’autres fonctions en résultats.

Dans le cas le plus simple, on se contente de déclarer le type comme étant Function. Cependant, un fonction acceptant elle-même des paramètres, il peut être utile de préciser quels sont ces derniers. Notamment lorsqu’on définit un callback.

/**
 * Get a user from database
 * @param { String } userId
 * @param { Function(err<Error | Null>, user<Object>) } callback
 */
function getUser(userId, (err, user) => {
    …
})

Alternativement, vous pouvez préciser les paramètres de la fonction dans la description.

* @param { Function } callback { err, width, ratio, color }

Nombre de paramètres variable

On utilise pour cela la même syntaxe que le spread operator de JavaScript. ...parameter. Un exemple concret acceptant un nombre variable de paramètres pouvant être de deux types diférents.

/**
 * Check if all DOM elements exist, call ƒn with said els if they exist
 * @param { Function } fn
 * @param { ...HTMLElement | ...NodeList }
 */
export function callFnWDomElsIfExist(fn, ...els) {
    …
}

Combiner les tous !

Les déclarations sont tout à fait combinables, ainsi vous pouvez déclarer des types composés.

// date ou array de dates de rendez-vous, également optionnel
* @param { Date | Date[] } [appointmentDates]

// tableau pouvant contenir des objets ou des strings
* @param { Array.<String|Object> } paramsList

// paramètre optionnel d'un objet
/**
 * Signup a new user
 * @param { Object } params
 * @param { String } params.[gender]
 */

// tableau imbriqué
/**
 * Compute student's mean grade
 * @param { Object[] } students
 * @param { String } students[].name
 * @param { Number[] } students[].mathGrades
 * @param { Number[] } students[].frenchGrades
 */

La seule limite est votre imagination (et la complexité de votre modèle de données).

Les classes

Personne n’a oublié que l’ES6 nous a donné les outils pour déclarer des classes. Si vous n’êtes pas au parfum, n’hésitez pas à jeter lire ou relire mon article dédié à la programmation objet en JavaScript.

Il arrive donc que nous ayons à documenter des classes. Dans une classe, on peut documenter trois niveaux :

Le constructeur et les méthodes se documentent de manière classique, comme n’importe quelle fonction. En revanche, il existe des mots-clefs spécifiques pour la classe.

/**
 * @class
 * @classdesc Create an error for bad HTTP request (contain http status to send err back to the client)
 */
class BadRequestError extends Error {
    …
}

D’après la documentation, il est possible de décrire sur la première ligne ce que fait le constructeur, je trouve cependant plus intuitif et direct de le documenter directement à l’intérieur du code de la classe.

Les valeurs de retour

Jusque là, nous avons documenté les paramètres des fonctions. Cependant, dans la plupart des cas, une fonction retourne une valeur. Si ce n’est pas le cas, c’est que votre fonction est impure, qu’elle a donc des effets de bord. C’est parfois inévitable, mais c’est à éviter au maximum dans un contexte de programmation fonctionnelle.

Dans le cas où la valeur de retour est simple, il suffit de préciser le type en utilisant @return.

/**
 * Get the first matching element
 * @param { String } selector CSS selector
 * @return { HTMLElement }
 */
export function find(selector) {
    return document.querySelector(selector);
}

En outre, une fonction peut retourner différentes valeurs selon le contexte, par exemple un nombre en cas de succès ou une erreur en cas de problème. Là encore, la logique vu jusqu’alors s’applique.

* @return { (Number | Error) }

Par ailleurs, on a de plus en plus recours aux promesses, la fonction retourne donc une promesse, laquelle peut être dans plusieurs états :

Dans le cas le plus simple, on spécifie simplement que la fonction retourne une promesse. On sait dès lors, car c’est la nature même d’une promesse qu’elle peut réussir ou échouer.

/**
 * Get comments for a given article id (w/ authors names)
 * @param { Number } articleId
 * @return { Promise }
 */
function getForId(id) {
    …
}

Cependant, dans de nombreux cas, il est souhaitable de documenter les différents états de manière plus précise. Dès lors, on adopte la même syntaxe que pour documenter les objets.

/**
 * Approve comment
 * @param { Number } commentId
 * @param { String } userSecret
 * @return { Promise }
 * @return { Promise.resolve<String> } articleId
 * @return { Promise.reject<Error> } knex Err or BadRequestError
 */
function approve(commentId, userSecret) {
    …
}

Conclusion

Nous avons vu comment commenter la plupart des cas que vous rencontrerez. Les commentaires vous permettent non seulement d’améliorer votre compréhension du code et de relire et maintenir plus vite et efficacement une base de code, mais aussi de générer une vraie documentation.

Par exemple, dans le cas d’une bibliothèque à destination d’autres développeurs, vous pouvez générer une documentation grâce à JSdoc. Une application en CLI vous permet de dynamiquement générer la doc liée à votre projet. C’est ce que j’utilise pour la documentation de ƒlightDom.

Dans la même veine, vous pouvez documenter vos API avec APIdoc. C’est la solution que j’utilise pour documenter les endoints de Jamments.

Commentaires

Rejoignez la discussion !

Vous pouvez utiliser Markdown pour les liens [ancre de lien](url), la mise en *italique* et en **gras**. Enfin pour le code, vous pouvez utiliser la syntaxe `inline` et la syntaxe bloc

```
ceci est un bloc
de code
```