Quand on débute en programmation, on nous répète qu'il faut commenter son code autant que possible. Mais très vite, on tombe sur un article qui affirme exactement le contraire. Perdu ? Cet article vous donnera une vision claire des moments où il faut commenter, et de ceux où il vaut mieux s'en abstenir.
Le principe est simple : laissez le langage et le runtime garantir, le plus tôt possible, la véracité de ce que vous documentez sur le logiciel.

Nos robots gardiens
Je vais commencer par une fonction non documentée et l'enrichir progressivement, dans l'ordre où je procède habituellement. D'abord les bases, puis les mécanismes automatisés qui garantissent que la documentation dit vrai, et enfin les formes plus faibles de documentation, à utiliser quand l'automatisation n'est pas envisageable.
1\. Aucune documentation
Partons de cette fonction toute simple, p!, la factorielle, tout droit sortie des exercices de programmation de base. L'exemple est en Python, mais le principe vaut aussi pour les autres langages.
def fact(p):
ret = 1
if x == 0:
return 1
for i in range(1, x + 1):
ret *= i
return ret
Vous pourriez être tenté d'ajouter un commentaire du type :
""" Ceci est la factorielle """
Mais les commentaires sont souvent ignorés, et celui-ci devient redondant dès que le nommage est correct.
2\. Le nommage
Modifiez la fonction ainsi :
def factorial(p):...
La documentation est désormais intégrée au code exécuté, et non greffée à côté. Quiconque souhaite utiliser votre fonction doit lire ce nom. Voilà une excellente raison pour laquelle les fonctions doivent être courtes et n'avoir qu'une seule responsabilité : le nom de la fonction peut alors rester proche du code et le décrire fidèlement.
Toute autre amélioration aidant le lecteur à comprendre votre code (par exemple, l'usage de membres privés là où c'est pertinent) constitue une forme honorable de documentation.
Renommons ensuite le paramètre en n, pour rappeler aux utilisateurs qu'il s'agit d'un entier.
def factorial(n):...
Après tout, bien que la factorielle ne soit définie que pour les entiers, on peut l'étendre à des valeurs non entières. La fonction Gamma est en quelque sorte une factorielle étendue.
Un utilisateur expérimenté pourrait se demander si elle est utilisée dans notre implémentation, et à juste titre : scipy.special.factorial, par exemple, retourne effectivement une valeur pour des entrées float non entières, en s'appuyant sur l'interpolation Gamma de la factorielle. Il faut donc préciser que ce n'est pas ce que nous proposons ici.

La fonction Gamma étend la factorielle à presque tous les nombres réels (et complexes).
3\. Les type hints
Le nom du paramètre est une forme de commentaire, et vous pourriez ajouter un commentaire indiquant qu'il s'agit d'un int (comme dans scipy.special.factorial). Mais il existe un meilleur moyen de signaler aux lecteurs que le paramètre est un entier : les type hints.
def factorial3(n: int) -> int:...
L'outillage de développement Python vous signalera la plupart des erreurs. Le principe : laissez l'ordinateur documenter à votre place et limitez la dépendance au cerveau humain. Par exemple, si votre implémentation refuse le float 3.0 en lieu et place d'un int, le type hint le documentera immédiatement, ce que vous pourriez oublier de faire autrement.
Les commentaires peuvent être obsolètes ou tout simplement faux. Par exemple, scipy.special.factorial (loin de moi l'idée de critiquer SciPy — c'est une excellente bibliothèque !) indique dans son commentaire n : int, mais accepte en réalité des float et retourne même une valeur conforme à la fonction Gamma.
Le typage à la compilation
Nos exemples sont en Python, mais si vous utilisez un langage à typage statique comme Java ou C++, le compilateur bloquera de nombreuses erreurs dès la phase de développement et garantira ainsi, le plus tôt possible, l'exactitude de cette " documentation ".
Cela dit, les assertions restent plus efficaces pour vérifier des contraintes plus fines, comme l'interdiction des nombres négatifs en entrée, sauf à utiliser un langage doté d'un système de types plus avancé.
4\. Assertions, exceptions ou pré- et post-conditions
On peut faire mieux que les type hints. Affirmez par une assertion que la valeur est un entier, et qu'elle n'est pas négative. Ce n'est plus seulement l'outillage de développement qui vous fait un clin d'œil : le runtime stoppe net les erreurs au moment où elles surviennent.
def factorial4(n: int) -> int:
assert isinstance(n, int), f"{n} n'est pas un entier et n'est donc pas pris en charge."
assert n >= 0, f"{n} est négatif et n'est donc pas pris en charge."
...
Il existe différentes manières de lever l'erreur.
Comme il ne sert à rien de laisser de telles erreurs se propager, gardez les assertions activées. Vous pouvez aussi lever une ValueError. Et si vous utilisez un langage ou une bibliothèque qui prend en charge la programmation par contrat, avec pré- et post-conditions (une forme structurée d'assertion), cela fera également l'affaire.
@icontract.require(lambda n: isinstance(n, int) and n>=0, "n doit être un entier non négatif")
def factorial_precondition(n: int) -> int:
return __iterative_factorial(n)
À ce stade, l'ordinateur vérifie réellement notre travail. Mais les assertions et autres exceptions ont un second mérite : elles constituent une forme de documentation qui ne peut pas mentir. Le lecteur sait que ce qu'affirme une assertion est vrai.
Pour revenir à notre piñata, scipy.special.factorial, le commentaire indique : " Si n < 0, la valeur retournée est 0. "
Techniquement, c'est vrai pour cette implémentation, mais c'est déroutant pour un utilisateur qui sait que la factorielle n'est pas définie sur les nombres négatifs. Même la fonction Gamma, extension de la factorielle, bien que définie sur les nombres réels et même complexes, ne l'est pas sur les entiers négatifs. Mieux vaut lever une erreur et indiquer clairement ce qui est autorisé et ce qui ne l'est pas.
5\. Les tests unitaires
Les tests unitaires ressemblent aux assertions : ils vérifient que le système fait ce que vous attendez pendant le développement. Comme documentation, ils sont moins efficaces que les assertions, car ils sont éloignés du code, à la fois par les fichiers où ils résident et par le moment où ils s'exécutent. Les autres développeurs ont moins de chances de les lire.
Cela dit, si les développeurs lancent les tests en continu, comme ils le devraient, ils repèrent vite les erreurs sans avoir à fouiller dans les commentaires.
Par exemple, notre simple factorielle itérative est cassée ! Elle retourne la valeur 1 pour les valeurs négatives, ce qui est manifestement faux, tant pour la factorielle que pour son extension Gamma. Ce test montrera que l'on attend une erreur mais que l'on obtient malheureusement une valeur de retour.
def test_factorial():
with pytest.raises(Exception):
factorial(-2)
Vous pouvez même intégrer des tests unitaires miniatures à l'intérieur des commentaires grâce à doctest, une fonctionnalité native de Python 3. Le test se trouve ainsi au plus près du code, là où il joue le mieux son rôle de documentation (au risque parfois d'alourdir la lecture).
def factorial_doctest(n) -> int:
"""
Lancez ceci avec python -m doctest -v factorial.py
>>> factorial_doctest(3)
>>> factorial_doctest(0)
>>> factorial_doctest(1.5)
Traceback (most recent call last):
...
TypeError: 'float' object cannot be interpreted as an integer
"""
return __iterative_factorial(n)
Les tests d'intégration, eux, sont encore plus éloignés du code et donc moins adaptés pour le documenter. Ils décrivent plutôt le comportement global du système.
6\. Le logging
Si un incident doit survenir et que vous n'avez aucun moyen de le prévenir en amont, vous pouvez au moins le consigner dans un log. Retour à SciPy.
Si vous passez un float, voici ce que vous obtenez sur la sortie d'erreur standard :
DeprecationWarning: Using factorial() with floats is deprecated
Apparemment, ils ont utilisé une implémentation basée sur Gamma puis, réalisant que seuls les entiers devaient être pris en charge, ils ont voulu restreindre l'entrée à int. Faute de pouvoir le faire à cause des applications dépendantes, ils écrivent au moins l'avertissement à un endroit où il a une chance d'être lu. Les développeurs ne lisent pas toujours la documentation, mais quand ils rencontrent un problème, ils consultent les logs.
7\. Les commentaires
Nous arrivons à notre avant-dernier recours : le commentaire. Il a sa place, mais seulement, d'après mon expérience, dans ces trois cas :
a. Les API publiques doivent être documentées par des commentaires en ligne, suivis d'une génération HTML via Doxygen pour Python, Javadoc pour Java, etc. Structurez les commentaires avec des balises, en suivant le principe selon lequel, dès qu'un ordinateur peut vérifier la structure, il doit le faire.
Voici un exemple de commentaires structurés en Python :
:param n: Un entier non négatif
:return: Le produit de tous les entiers de 1 jusqu'à n inclus ; ou pour 0, retourne 1.
En revanche, lorsque la fonction n'est pas une API publique, utilisez ces champs structurés avec parcimonie : ces commentaires servent à transmettre un message précis sur quelque chose d'inhabituel, pas à documenter méticuleusement tous les aspects de l'usage.
b. Les faits surprenants méritent d'être documentés. Par exemple, les utilisateurs ne savent pas forcément que la factorielle de zéro est définie comme valant 1, puisque cela ne semble pas cadrer avec la définition habituelle de la factorielle comme " le produit de tous les nombres de 1 à n ". De même, les contournements étranges et les bidouilles peu élégantes méritent un commentaire dans le code.
c. Les algorithmes doivent être nommés, car l'ordinateur ne peut ni montrer ni garantir quel algorithme est utilisé, et le lecteur ne saura pas forcément l'identifier d'un coup d'œil. Par exemple, la factorielle peut être calculée par multiplication, ou approximée par divers algorithmes pour Gamma. Un utilisateur ayant des besoins numériques exigeants pourrait vouloir le savoir.
def factorial(n: int) -> int:
"""
Ceci est une simple implémentation itérative de la factorielle.
L'entrée doit être un entier non négatif.
Notez que la factorielle de zéro est définie comme valant 1.
"""
8\. La documentation externe
Les commentaires étaient l'avant-dernier recours ; la documentation externe est le tout dernier. RTFM est un bon slogan, mais peu de gens lisent réellement le manuel : trop éloigné du code, il devient vite obsolète. Cela dit, si vous avez besoin d'une vue d'ensemble des fonctionnalités ou de tutoriels, vous pouvez les placer dans un document maintenu séparément du code.
Mais attention : vous devez relire et réviser ces documents en permanence si vous voulez qu'ils reflètent un code en perpétuelle évolution.

Plus c'est proche du code et automatisé, mieux c'est, mais chaque approche a son utilité.
La documentation doit dire la vérité. Votre outillage de langage et votre runtime offrent de nombreux moyens de garantir la fiabilité : servez-vous-en. Placez la documentation au plus près du code et faites en sorte que la vérification automatisée de son exactitude intervienne tôt dans le processus de développement.
Le code exécutable des différentes implémentations est disponible ici, avec les tests unitaires : https://github.com/doitintl/commenting
Pour rester en contact, suivez-nous sur le DoiT Engineering Blog , la page LinkedIn DoiT et le compte Twitter DoiT . Pour découvrir nos opportunités de carrière, rendez-vous sur https://careers.doit-intl.com .