Chapitre 721 minDébutant
Documenter son code : docstring Python, JSDoc et commentaires XML en C#
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- distinguer un commentaire utile d'un commentaire qui paraphrase ;
- écrire la docstring d'une fonction Python, avec un exemple testé ;
- documenter une fonction JavaScript avec JSDoc ;
- documenter une méthode C# avec les commentaires XML ;
- générer une documentation à partir du code.
4 exercices corrigés
Documenter son code, c'est écrire au plus près du code ce qu'un autre développeur doit savoir pour l'utiliser et le modifier : le contrat de chaque fonction (ce qu'elle reçoit, ce qu'elle renvoie, ce qui peut échouer) et le pourquoi des choix qui ne se devinent pas. Ce chapitre distingue les commentaires utiles des commentaires inutiles, puis présente les trois formats que tu rencontreras au BTS : la docstring en Python, JSDoc en JavaScript et les commentaires XML en C#, avec la génération automatique d'une documentation.
Commentaire utile ou commentaire inutile
Le code dit ce qu'il fait ; un commentaire utile dit pourquoi, ou ce que le code ne peut pas dire.
# Inutile : il répète le code.
nb_tickets += 1 # ajoute 1 au nombre de tickets
# Utile : il donne une règle métier qu'on ne devine pas.
# Un ticket « critique » doit être pris en charge en moins d'une heure (contrat de service
# signé avec la direction le 01/09/2026) : on le place en tête de file.
if ticket.priorite == "critique":
file.insert(0, ticket)
# Utile : il explique un contournement et pointe vers sa source.
# Le serveur d'impression renvoie parfois 0 page pour un travail réussi (ticket 2) :
# on considère comme réussi tout travail sans code d'erreur.
Quelques règles :
- Un bon nom vaut mieux qu'un commentaire.
if p.e is None: # poste en stockse réécritif poste.est_en_stock():. Le commentaire disparaît, l'intention reste. - Le commentaire suit le code. Un commentaire faux après une modification est pire que pas de commentaire : le lecteur croit le commentaire.
- Pas de code mis en commentaire « au cas où » : Git le garde dans l'historique. Supprime-le.
- Un
TODOporte un numéro de ticket (# TODO(#42) : gérer les postes prêtés), sinon personne ne le traitera jamais.
Le contrat d'une fonction
Un développeur qui appelle ta fonction ne devrait pas avoir à lire son code. Il a besoin de son contrat :
| Élément | Question | Exemple pour numero_inventaire |
|---|---|---|
| Rôle | Que fait-elle, en une phrase ? | Renvoie le numéro d'inventaire d'un poste |
| Paramètres | Quoi, de quel type, dans quelle unité, avec quelles limites ? | numero : entier de 1 à 9999 |
| Valeur renvoyée | Quoi, sous quelle forme ? | Une chaîne PC- suivie de quatre chiffres |
| Erreurs | Que se passe-t-il si les conditions ne sont pas respectées ? | Lève une erreur si numero est hors limites |
| Effets de bord | Modifie-t-elle quelque chose (fichier, base, variable globale) ? | Aucun |
| Exemple | Un appel et son résultat | numero_inventaire(16) renvoie 'PC-0016' |
C'est ce qu'attend le référentiel du bloc 2 SLAM quand il demande que « les composants logiciels sont documentés de manière à être réutilisés ». Le cours Algorithmique introduit cet en-tête dès les premières fonctions ; voici comment l'écrire dans chaque langage, pour que les outils le comprennent.
Python : la docstring
Une docstring est une chaîne de caractères placée en première instruction d'un module, d'une classe ou d'une fonction. Python la range dans l'attribut __doc__ de l'objet, et la fonction help() l'affiche. Ses règles viennent de la PEP 257, la convention officielle :
- toujours des triples guillemets doubles
"""; - une première ligne de résumé, qui tient sur une ligne et se termine par un point ; elle prescrit l'effet comme un ordre (« Renvoie… », et non « Cette fonction renvoie… ») ;
- pour une docstring longue : une ligne vide après le résumé, puis la description, et les guillemets fermants sur leur propre ligne ;
- pour une fonction : le comportement, les arguments, la valeur renvoyée, les effets de bord, les exceptions levées et les restrictions d'utilisation.
Pour la partie détaillée, plusieurs styles existent (Google, NumPy, reStructuredText). Choisis-en un par projet et garde-le. Voici le style Google, le plus lisible, sur une fonction de l'inventaire de Sorbier :
def numero_inventaire(numero: int) -> str:
"""Renvoie le numéro d'inventaire d'un poste, au format PC-0000.
Le numéro est complété à gauche par des zéros jusqu'à quatre chiffres,
comme dans la colonne num_inventaire de la base parc.
Args:
numero: numéro séquentiel du poste, de 1 à 9999.
Returns:
Le numéro d'inventaire, par exemple « PC-0016 ».
Raises:
ValueError: si numero n'est pas compris entre 1 et 9999.
Examples:
>>> numero_inventaire(16)
'PC-0016'
>>> numero_inventaire(1234)
'PC-1234'
>>> numero_inventaire(0)
Traceback (most recent call last):
...
ValueError: numéro hors limites : 0
"""
if not 1 <= numero <= 9999:
raise ValueError(f"numéro hors limites : {numero}")
return f"PC-{numero:04d}"
- Les annotations de type (
numero: int,-> str) disent le type ; la docstring dit le sens et les limites (de 1 à 9999), que le type ne peut pas exprimer. - Les titres de sections (
Args,Returns,Raises,Examples) restent en anglais : ce sont des mots-clés que les outils reconnaissent. Le texte, lui, est en français. help(numero_inventaire)dans l'interpréteur affiche la signature et la docstring ; VS Code l'affiche au survol de chaque appel.
Des exemples qui se vérifient tout seuls. Les lignes qui commencent par >>> sont des doctests : le module doctest de la bibliothèque standard exécute chaque exemple et compare le résultat à la ligne suivante.
python -m doctest -v inventaire.py
1 item passed all tests:
3 tests in inventaire.numero_inventaire
3 tests in 2 items.
3 passed.
Test passed.
(Ce sont les dernières lignes : avec -v, doctest affiche d'abord chaque exemple essayé.)
Si quelqu'un modifie la fonction et que l'exemple devient faux, le test échoue : une documentation qui ment est détectée. Voici ce qu'affiche doctest si l'on écrit 'PC-16' au lieu de 'PC-0016' dans le premier exemple :
Failed example:
numero_inventaire(16)
Expected:
'PC-16'
Got:
'PC-0016'
Pour produire un site à partir des docstrings, les outils les plus utilisés sont Sphinx (avec ses extensions autodoc et napoleon, qui comprend le style Google) et pdoc, plus simple. Le cours Python approfondit le langage.
JavaScript : JSDoc
JSDoc est la convention de documentation de JavaScript : un commentaire qui commence par /**, placé juste avant la fonction, avec des étiquettes qui commencent par @.
// @ts-check
/**
* Renvoie le numéro d'inventaire d'un poste, au format PC-0000.
*
* @param {number} numero - Numéro séquentiel du poste, de 1 à 9999.
* @returns {string} Le numéro d'inventaire, par exemple « PC-0016 ».
* @throws {RangeError} Si numero n'est pas un entier compris entre 1 et 9999.
* @example
* numeroInventaire(16); // 'PC-0016'
*/
export function numeroInventaire(numero) {
if (!Number.isInteger(numero) || numero < 1 || numero > 9999) {
throw new RangeError(`numéro hors limites : ${numero}`);
}
return `PC-${String(numero).padStart(4, '0')}`;
}
| Étiquette | Rôle |
|---|---|
@param {type} nom - description | Un paramètre ; le nom doit être celui de la fonction |
@returns {type} description | La valeur renvoyée |
@throws {type} description | Une erreur que la fonction peut lever |
@example | Un exemple d'appel, sur les lignes suivantes |
@typedef et @property | Décrire la forme d'un objet (un poste, un ticket) une fois, pour la réutiliser |
@deprecated | Signaler qu'une fonction ne doit plus être utilisée, et par quoi la remplacer |
@see | Renvoyer vers une autre fonction ou une page |
Les types de JSDoc vérifiés par l'éditeur. VS Code utilise le moteur de TypeScript pour lire les commentaires JSDoc : il affiche la documentation au survol et complète les appels. Avec la ligne // @ts-check en tête du fichier, il vérifie aussi les types déclarés. Un appel numeroInventaire('16') est souligné en rouge, avec le message :
Argument of type 'string' is not assignable to parameter of type 'number'.
La documentation devient un garde-fou : l'erreur apparaît en écrivant, pas en production.
Générer une documentation HTML. L'outil jsdoc lit les commentaires et produit un site, par défaut dans le dossier out/ :
npx jsdoc src -r -d docs/code
-r parcourt les sous-dossiers de src, -d choisit le dossier de sortie. Pour une API Express, JSDoc documente les fonctions et les modules ; les routes, elles, se documentent avec OpenAPI (chapitre 8). Le cours Node.js relie les deux.
C# : les commentaires de documentation XML
En C#, un commentaire de documentation commence par trois barres obliques /// et contient des balises XML. Visual Studio et VS Code insèrent le squelette quand tu tapes /// au-dessus d'une méthode.
namespace Sorbier.Parc;
/// <summary>
/// Outils pour l'inventaire du parc informatique.
/// </summary>
public static class Inventaire
{
/// <summary>
/// Renvoie le numéro d'inventaire d'un poste, au format PC-0000.
/// </summary>
/// <param name="numero">Numéro séquentiel du poste, de 1 à 9999.</param>
/// <returns>Le numéro d'inventaire, par exemple <c>PC-0016</c>.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="numero"/> n'est pas compris entre 1 et 9999.
/// </exception>
/// <example>
/// <code>
/// string num = Inventaire.NumeroInventaire(16); // "PC-0016"
/// </code>
/// </example>
public static string NumeroInventaire(int numero)
{
ArgumentOutOfRangeException.ThrowIfLessThan(numero, 1);
ArgumentOutOfRangeException.ThrowIfGreaterThan(numero, 9999);
return $"PC-{numero:D4}";
}
}
| Balise | Rôle |
|---|---|
<summary> | Le résumé, affiché par IntelliSense ; le minimum pour tout type et tout membre public |
<param name="…"> | Un paramètre ; le compilateur vérifie que le nom existe |
<returns> | La valeur renvoyée |
<exception cref="…"> | Une exception possible ; le compilateur vérifie que le type existe |
<example>, <code>, <c> | Un exemple ; du code sur plusieurs lignes ; du code dans une phrase |
<paramref name="…"/>, <see cref="…"/> | Citer un paramètre ; renvoyer vers un autre type ou membre |
<remarks> | Des précisions qui ne tiennent pas dans le résumé |
<inheritdoc/> | Reprendre la documentation de la classe mère ou de l'interface |
Microsoft recommande de documenter tous les types et membres publics, avec au moins un <summary>, en phrases complètes terminées par un point.
Le compilateur vérifie ta documentation. Ajoute une ligne au fichier de projet :
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
À la compilation, deux choses se produisent. Un fichier Parc.xml est produit à côté de l'assembly : il contient tous les commentaires, et les outils (IntelliSense dans les projets qui utilisent ta bibliothèque, générateurs de documentation) le lisent. Et le compilateur signale les écarts par des avertissements, dont trois reviennent souvent :
| Avertissement | Cause |
|---|---|
| CS1591 | Un type ou un membre public n'a pas de commentaire XML |
| CS1572 | Une balise <param> cite un paramètre qui n'existe pas (souvent après un renommage) |
| CS1573 | Un paramètre n'a pas de balise <param>, alors que d'autres paramètres en ont |
Pour produire un site de documentation à partir du fichier XML et de pages Markdown, l'outil de référence de l'écosystème .NET est DocFX. Le cours POO en C# utilise ces commentaires sur ses classes métier.
PHP : PHPDoc, en bref
PHP suit la même logique avec PHPDoc : un commentaire /** … */ avec les étiquettes @param, @return et @throws. Il complète les types natifs de PHP quand ils ne suffisent pas, par exemple pour dire qu'un tableau contient des objets Poste (@return Poste[]). L'outil phpDocumentor en tire un site. Le cours Maintenance applicative en donne des exemples.
| Python | JavaScript | C# | PHP | |
|---|---|---|---|---|
| Où | Première instruction de la fonction | Commentaire /** */ avant la fonction | Commentaires /// avant le membre | Commentaire /** */ avant la fonction |
| Paramètre | Section Args: | @param {type} nom | <param name="nom"> | @param type $nom |
| Valeur renvoyée | Section Returns: | @returns {type} | <returns> | @return type |
| Erreurs | Section Raises: | @throws {type} | <exception cref="…"> | @throws Type |
| Exemple vérifiable | Doctest (>>>) | Non, par défaut | Non, par défaut | Non, par défaut |
| Générer un site | Sphinx, pdoc | jsdoc | DocFX | phpDocumentor |
Ce qui ne va pas dans le code
Les commentaires documentent le code ligne par ligne et fonction par fonction. Ce qui concerne le projet entier va ailleurs :
- l'installation et le lancement : le README (chapitre 5) ;
- l'architecture (les couches, les composants, leurs échanges) :
docs/architecture.md, avec un schéma ; - les décisions importantes : une ADR (Architecture Decision Record), une page courte par décision, avec le contexte, la décision, les options écartées et les conséquences. « Pourquoi MySQL et pas MongoDB » s'écrit une fois dans une ADR, pas dans un commentaire perdu au milieu du code.
Exercices
Exercice 1 sur 4 Garder ou supprimer Réussi À revoir
Pour chaque commentaire, dis s'il faut le garder, le supprimer, le remplacer par un meilleur nom ou le compléter.
total = total + prix # ajoute le prix au total# L'API de l'opérateur limite à 10 requêtes par seconde : on attend 100 ms entre deux appels.if d > 30: # si le ticket a plus de 30 jours# ancienne version : total = calcul_v1(lignes)# TODO : gérer les postes prêtés
Voir la réponseMasquer la réponse de l'exercice 1
- Supprimer : il répète le code.
- Garder : il explique une contrainte extérieure qu'on ne devine pas en lisant le code.
- Remplacer par un nom :
if age_ticket_jours > 30:rend le commentaire inutile. - Supprimer : l'ancien code est dans l'historique Git.
- Compléter : un
TODOsans ticket ne sera jamais traité.# TODO(#42) : gérer les postes prêtés, avec le ticket 42 qui décrit le besoin.
Tu avais trouvé ?
Exercice 2 sur 4 Écrire une docstring testée Réussi À revoir
Écris la docstring, au style Google, de cette fonction, avec deux doctests : une intervention de 45 minutes et une de 20 minutes, au taux horaire de 52 €. Calcule les résultats attendus avant de lancer doctest.
def cout_intervention(duree_min: int, taux_horaire: float) -> float:
if duree_min <= 0:
raise ValueError("la durée doit être strictement positive")
return round(duree_min / 60 * taux_horaire, 2)
Voir la réponseMasquer la réponse de l'exercice 2
45 minutes valent 0,75 heure : 0,75 × 52 = 39 €, que Python affiche 39.0. 20 minutes valent 1/3 d'heure : 52 ÷ 3 = 17,333…, arrondi au centime : 17.33.
def cout_intervention(duree_min: int, taux_horaire: float) -> float:
"""Calcule le coût d'une intervention, arrondi au centime.
Args:
duree_min: durée de l'intervention en minutes, strictement positive.
taux_horaire: coût d'une heure de technicien, en euros.
Returns:
Le coût en euros, arrondi à deux décimales.
Raises:
ValueError: si duree_min est nulle ou négative.
Examples:
>>> cout_intervention(45, 52.0)
39.0
>>> cout_intervention(20, 52.0)
17.33
"""
if duree_min <= 0:
raise ValueError("la durée doit être strictement positive")
return round(duree_min / 60 * taux_horaire, 2)
python -m doctest -v affiche les deux tests réussis. Si tu avais écrit 39 au lieu de 39.0, le doctest échouerait : il compare le texte affiché, et une division renvoie toujours un nombre à virgule (float) en Python.
Tu avais trouvé ?
Exercice 3 sur 4 Un JSDoc qui ment Réussi À revoir
Trouve les trois erreurs de ce commentaire.
/**
* Indique si un poste est en stock.
* @param {Object} p - Le poste.
* @returns {string} true si le poste n'est affecté à personne.
*/
function estEnStock(poste) {
return poste.affecteA === null;
}
Voir la réponseMasquer la réponse de l'exercice 3
- Le nom du paramètre : le commentaire dit
p, la fonction attendposte. Les outils ne relient plus la description au paramètre. - Le type renvoyé : la fonction renvoie un booléen ; il faut
@returns {boolean}. - Le type du paramètre est trop vague :
{Object}ne dit pas quels champs sont lus. Mieux : décrire la forme une fois avec@typedefet l'utiliser.
/**
* @typedef {object} Poste
* @property {string} numInventaire - Numéro d'inventaire, par exemple PC-0008.
* @property {string | null} affecteA - Adresse de l'utilisateur, null si en stock.
*/
/**
* Indique si un poste est en stock.
* @param {Poste} poste - Le poste à tester.
* @returns {boolean} true si le poste n'est affecté à personne.
*/
function estEnStock(poste) {
return poste.affecteA === null;
}
Tu avais trouvé ?
Exercice 4 sur 4 Les avertissements du compilateur Réussi À revoir
Le projet a GenerateDocumentationFile à true. Quels avertissements produit ce code, placé dans une classe publique déjà documentée, et comment le corriger ?
/// <summary>
/// Calcule le coût d'une intervention, arrondi au centime.
/// </summary>
/// <param name="dureeMin">Durée en minutes.</param>
/// <param name="taux">Taux horaire.</param>
public static decimal CoutIntervention(int dureeMinutes, decimal tauxHoraire)
=> Math.Round(dureeMinutes / 60m * tauxHoraire, 2);
public static bool EstEnStock(int? idEmploye) => idEmploye is null;
Voir la réponseMasquer la réponse de l'exercice 4
Cinq avertissements, de trois sortes :
- CS1572 deux fois : les balises citent
dureeMinettaux, qui n'existent pas (les paramètres ont été renommés sans mettre le commentaire à jour) ; - CS1573 deux fois :
dureeMinutesettauxHorairen'ont pas de balise<param>; - CS1591 une fois :
EstEnStockest publique et n'a pas de commentaire.
Correction : renommer les balises en <param name="dureeMinutes"> et <param name="tauxHoraire">, préciser les unités (« en minutes, strictement positive », « en euros »), ajouter <returns>, et documenter EstEnStock (« Indique si un poste est en stock, c'est-à-dire affecté à aucun salarié »). Ces avertissements sont précieux : ils détectent exactement la documentation qui ne suit plus le code.
Tu avais trouvé ?