Documentation
Parfois, pour expliquer un code complexe, il peut être nécessaire de documenter certains passages et d’ajouter un commentaire explicatif. Il y a cependant une énorme différence entre les commentaires associés au code et la documentation du code :
- Les commentaires décrivent comment le code fonctionne,
- La documentation du code décrit comment utiliser le code.
Pour que le code soit utilisé correctement, il est dès lors important de documenter :
- Ce que représente une classe ou une API,
- Les attentes d’une interface : codes d’erreurs, statuts possibles, etc.
Les commentaires peuvent être utiles lorsque le code n’est pas évident à lire ou lorsque certaines optimisations auraient été appliquées dans un soucis de performances. Dans la majorité des cas, si des commentaires doivent être rédigés pour que le code devienne lisible, c’est que ce code n’est pas correctement écrit. Et à moins d’avoir une hygiène de développement aux petits oigons, une décorrélation entre le code et ses commentaires peut souvent être constatée :
# Check that the maximum length of the array if less than 8if len(array) < 10: ...
Il y a donc une juste mesure à prendre entre “tout documenter” (les modules, les paquets, les classes, les fonctions, méthodes, …) et “bien documenter”. Il est ainsi inutile :
- D’ajouter des watermarks, auteurs, … : Git ou tout VCS s’en sortira très bien et sera beaucoup plus efficace que n’importe quelle chaîne de caractères que vous pourriez indiquer et qui sera fausse dans six mois,
- De décrire quelque chose qui est évident : documenter la méthode
get_age()
d’une personne n’aura pas beaucoup d’intérêt, - De décrire un comportement au sein-même d’une fonction, avec un commentaire inline : c’est que ce comportement pourrait probablement être extrait dans une nouvelle fonction (qui, elle, pourra être documentée proprement).
En résumé, vous pouvez être obsédé par la documentation, mais le code reste la référence.
Il existe plusieurs types de balisages reconnus :
- RestructuredText
- Numpy
- Google Style (parfois connue sous l’intitulé
Napoleon
)
… mais tout système de balisage peut être reconnu, sous réseve de respecter la structure de la PEP257, qui donne des recommandations haut-niveau concernant la structure des docstrings : ce qu’elles doivent contenir et comment l’expliciter, sans imposer quelle que mise en forme de contenu que ce soit.
“A universal convention supplies all of maintainability, clarity, consistency, and a foundation for good programming habits too. What it doesn’t do is insist that you follow it against your will. That’s Python!”
Tim Peters on comp.lang.python, 2001-06-16
Les conventions décrivent ainsi la manière dont le formatage doit être utilisé, tandis que chaque format propose ensuite son propre balisage :
Les sections sont définies selon les types ci-dessous :
- Une courte description (de la classe, du module, de la fonction, …). Chaque module devrait avoir une docstring au tout début du fichier. Cette docstring peut s’étendre sur plusieurs lignes (Short summary)
- Des avertissements liés à la dépréciation: quand la fonctionnalité viendra à disparaitre (dans quelle version), les raisons de sa dépréciation, et les recommandations pour arriver au même résultat. (Deprecated)
- Une description plus précise des fonctionnalités proposées (et pas de la manière dont l’implémentation est réalisée). (Extended summary)
- Les paramètres attendus, leur type et une description (Parameters)
- La ou les valeurs de retour, accompagnées de leur type et d’une description (Returns)
- Les valeurs générées (yields) dans le cas de générateurs (Yields)
- Un objet attendu par la méthode
send()
(toujours dans le cas de générateurs) (Receives) - D’autres paramètres, notamment dans le cas des paramètres
*args
et**kwargs
. (Other parameters) - Les exceptions levées (Raises)
- Les avertissements destinés à l’utilisateur (Warnings)
- Une section “Pour continuer…” (See also)
- Des notes complémentaires (Notes)
- Des références (References)
- Des exemples (Examples)
RestructuredText
Numpy
Numpy sépare chaque section avec un pseudo-formatage de titre.
def foo(var1, var2, *args, long_var_name="hi", only_seldom_used_keyword=0, **kwargs): r"""Summarize the function in one line.
Several sentences providing an extended description. Refer to variables using back-ticks, e.g. `var`.
Parameters ----------
<parameter_name> : <parameter_type> <parameter_description>
Returns ------- type Explanation of anonymous return value of type ``type``. describe : type Explanation of return value named `describe`. out : type Explanation of `out`. type_without_description
Other Parameters ---------------- only_seldom_used_keyword : int, optional Infrequently used parameters can be described under this optional section to prevent cluttering the Parameters section. **kwargs : dict Other infrequently used keyword arguments. Note that all keyword arguments appearing after the first parameter specified under the Other Parameters section, should also be described under this section.
Raises ------ BadException Because you shouldn't have done that.
See Also -------- numpy.array : Relationship (optional). numpy.ndarray : Relationship (optional), which could be fairly long, in which case the line wraps here. numpy.dot, numpy.linalg.norm, numpy.eye
Notes ----- Notes about the implementation algorithm (if needed).
This can have multiple paragraphs.
You may include some math:
.. math:: X(e^{j\omega } ) = x(n)e^{ - j\omega n}
And even use a Greek symbol like :math:`\omega` inline.
References ---------- Cite the relevant literature, e.g. [1]_. You may also cite these references in the notes section above.
.. [1] O. McNoleg, "The integration of GIS, remote sensing, expert systems and adaptive co-kriging for environmental habitat modelling of the Highland Haggis using object-oriented, fuzzy-logic and neural-network techniques," Computers & Geosciences, vol. 22, pp. 585-588, 1996.
Examples -------- These are written in doctest format, and should illustrate how to use the function.
>>> a = [1, 2, 3] >>> print([x + 3 for x in a]) [4, 5, 6] >>> print("a\nb") a b """
pass
Napoleon
Les conventions proposées par Google nous semblent plus faciles à lire que du RestructuredText, mais sont parfois moins bien intégrées que les docstrings officiellement supportées (par exemple, clize ne reconnait que du RestructuredText ; l’auto-documentation de Django également).
L’exemple donné dans les guides de style de Google est celui-ci:
def fetch_smalltable_rows( table_handle: smalltable.Table, keys: Sequence[Union[bytes, str]], require_all_keys: bool = False,) -> Mapping[bytes, Tuple[str]]: """Fetches rows from a Smalltable.
Retrieves rows pertaining to the given keys from the Table instance represented by table_handle. String keys will be UTF-8 encoded.
Args: table_handle: An open smalltable.Table instance. keys: A sequence of strings representing the key of each table row to fetch. String keys will be UTF-8 encoded. require_all_keys: Optional; If require_all_keys is True only rows with values set for all keys will be returned.
Returns: A dict mapping keys to the corresponding table row data fetched. Each row is represented as a tuple of strings. For example:
{ b'Serak': ('Rigel VII', 'Preparer'), b'Zim': ('Irk', 'Invader'), b'Lrrr': ('Omicron Persei 8', 'Emperor') }
Returned keys are always bytes. If a key from the keys argument is missing from the dictionary, then that row was not found in the table (and require_all_keys must have been False).
Raises: IOError: An error occurred accessing the smalltable. """
Un exemple (encore) plus complet peut être trouvé dans le dépôt sphinxcontrib-napoleon.
Pour ceux que cela pourrait intéresser, il existe une extension pour Codium, comme nous le verrons juste après, qui permet de générer automatiquement le squelette de documentation d’un bloc de code:
Nous le verrons plus loin, Django permet de rendre la documentation immédiatement accessible depuis l’interface d’administration. Toute information pertinente peut donc lier le code à un cas d’utilisation concret, et rien n’est jamais réellement perdu.