web-dev-qa-db-fra.com

Commentaires Clean Code vs documentation de classe

J'ai des discussions avec mes nouveaux collègues concernant les commentaires. Nous aimons tous les deux Clean Code, et je suis parfaitement d'accord avec le fait que les commentaires de code en ligne doivent être évités et que la classe et les noms de méthodes doivent être utilisés pour exprimer ce qu'ils font.

Cependant, je suis un grand fan de l'ajout de petits résumés de classe qui essaient d'expliquer le but de la classe et ce qui est réellement représenté, principalement afin qu'il soit facile de maintenir le modèle principe de responsabilité unique . Je suis également habitué à ajouter des résumés d'une ligne aux méthodes qui expliquent ce que la méthode est censée faire. Un exemple typique est la méthode simple

public Product GetById(int productId) {...}

J'ajoute le résumé de méthode suivant

/// <summary>
/// Retrieves a product by its id, returns null if no product was found.
/// </summary

Je pense que le fait que la méthode renvoie null devrait être documenté. Un développeur qui souhaite appeler une méthode ne devrait pas avoir à ouvrir mon code pour voir si la méthode retourne null ou lève une exception. Parfois, cela fait partie d'une interface, donc le développeur ne sait même pas quel code sous-jacent est en cours d'exécution?

Cependant, mes collègues pensent que ce genre de commentaires est " odeur de code " et que "les commentaires sont toujours des échecs" ( Robert C. Martin ).

Existe-t-il un moyen d'exprimer et de communiquer ces types de connaissances sans ajouter de commentaires? Comme je suis un grand fan de Robert C. Martin, je suis un peu confus. Les résumés sont-ils les mêmes que les commentaires et donc toujours des échecs?

Ce n'est pas une question sur les commentaires en ligne.

84
Bjorn

Comme d'autres l'ont dit, il y a une différence entre les commentaires documentant l'API et les commentaires en ligne. De mon point de vue, la principale différence est qu'un commentaire en ligne est lu à côté du code , tandis qu'un commentaire de documentation est lu à côté du signature de tout ce que vous commentez.

Compte tenu de cela, nous pouvons appliquer le même principe SEC . Le commentaire dit-il la même chose que la signature? Regardons votre exemple:

Récupère un produit par son identifiant

Cette partie répète simplement ce que nous voyons déjà du nom GetById plus le type de retour Product. Cela soulève également la question de savoir quelle est la différence entre "obtenir" et "récupérer", et quel est le rapport code/commentaire portant sur cette distinction. C'est donc inutile et légèrement déroutant. Si quoi que ce soit, cela gêne la deuxième partie du commentaire réellement utile:

renvoie null si aucun produit n'a été trouvé.

Ah! C'est quelque chose que nous ne pouvons certainement pas savoir avec certitude juste à partir de la signature et fournit des informations utiles.


Maintenant, allez un peu plus loin. Lorsque les gens parlent de commentaires car le code sent, la question n'est pas de savoir si le code tel qu'il est a besoin d'un commentaire, mais si le commentaire indique que le code pourrait être mieux écrit, pour exprimer les informations dans le commentaire. C'est ce que signifie "odeur de code" - cela ne signifie pas "ne faites pas ça!", Cela signifie "si vous faites cela, cela pourrait être un signe qu'il y a un problème".

Donc, si vos collègues vous disent que ce commentaire sur null est une odeur de code, vous devriez simplement leur demander: "D'accord, comment dois-je exprimer cela alors?" S'ils ont une réponse réalisable, vous avez appris quelque chose. Sinon, cela tuera probablement leurs plaintes mortes.


En ce qui concerne ce cas spécifique, la question nulle est généralement bien connue pour être difficile. Il y a une raison pour laquelle les bases de code sont jonchées de clauses de garde, pourquoi les contrôles nuls sont une condition préalable populaire pour les contrats de code, pourquoi l'existence de null a été appelée une "erreur d'un milliard de dollars". Il n'y a pas beaucoup d'options viables. Un des plus populaires, cependant, trouvé en C # est le Try... convention:

public bool TryGetById(int productId, out Product product);

Dans d'autres langues, il peut être idiomatique d'utiliser un type (souvent appelé quelque chose comme Optional ou Maybe) pour indiquer un résultat qui peut ou non être là:

public Optional<Product> GetById(int productId);

Donc, d'une certaine manière, cette position anti-commentaire nous a amenés quelque part: nous avons au moins pensé si ce commentaire représentait une odeur et quelles alternatives pourraient exister pour nous.

Que nous devrions réellement les préférer à la signature originale est un tout autre débat, mais nous avons au moins des options pour exprimer par le code plutôt que de commenter ce qui se passe quand aucun produit n'est trouvé. Vous devriez discuter avec vos collègues des options qui, selon eux, sont les meilleures et pourquoi, et nous espérons pouvoir vous aider à aller au-delà des déclarations dogmatiques générales sur les commentaires.

116
Ben Aaronson

La citation de Robert C. Martin est sortie de son contexte. Voici la citation avec un peu plus de contexte:

Rien ne peut être aussi utile qu'un commentaire bien placé. Rien ne peut encombrer un module plus que des commentaires dogmatiques frivoles. Rien ne peut être aussi dommageable qu'un vieux commentaire cruel qui propage des mensonges et de la désinformation.

Les commentaires ne sont pas comme la liste de Schindler. Ils ne sont pas "du pur bien". En effet, les commentaires sont, au mieux, un mal nécessaire. Si nos langages de programmation étaient suffisamment expressifs, ou si nous avions le talent de manier subtilement ces langages pour exprimer notre intention, nous n'aurions pas besoin de beaucoup de commentaires - peut-être pas du tout.

La bonne utilisation des commentaires est de compenser notre incapacité à nous exprimer dans le code. Notez que j'ai utilisé l'échec de Word. C'est ce que je voulais dire. Les commentaires sont toujours des échecs. Nous devons les avoir parce que nous ne pouvons pas toujours trouver comment nous exprimer sans eux, mais leur utilisation n'est pas un motif de célébration.

Donc, lorsque vous vous retrouvez dans une position où vous devez écrire un commentaire, réfléchissez-y et voyez s'il n'y a aucun moyen de tourner les tables et de vous exprimer dans le code. Chaque fois que vous vous exprimez en code, vous devez vous tapoter le dos. Chaque fois que vous écrivez un commentaire, vous devriez grimacer et ressentir l'échec de votre capacité d'expression.

(Copié d'ici , mais la citation originale provient de Clean Code: A Handbook of Agile Software Craftsmanship )

Comment cette citation est réduite à "Les commentaires sont toujours des échecs" est un bon exemple de la façon dont certaines personnes prendront une citation sensée hors de leur contexte et la transformeront en un dogme stupide.


La documentation de l'API (comme javadoc) est censée documenter l'API afin que l'utilisateur puisse l'utiliser sans avoir à lire le code source. Dans ce cas, la documentation devrait expliquer ce que fait la méthode. Vous pouvez maintenant affirmer que "Récupère un produit par son identifiant" est redondant car il est déjà indiqué par le nom de la méthode, mais les informations que null peuvent être renvoyées sont certainement importantes à documenter, car cela ne figure dans aucun manière évidente.

Si vous voulez éviter la nécessité du commentaire, vous devez supprimer le problème sous-jacent (qui est l'utilisation de null comme valeur de retour valide), en rendant l'API plus explicite. Par exemple, vous pouvez renvoyer une sorte de type Option<Product>, De sorte que la signature de type elle-même communique clairement ce qui sera retourné au cas où le produit ne serait pas trouvé.

Mais dans tous les cas, il n'est pas réaliste de documenter une API entièrement uniquement via les noms de méthode et les signatures de type. Utilisez doc-comments pour toute information supplémentaire non évidente que l'utilisateur doit connaître. Prenons par exemple la documentation de l'API de DateTime.AddMonths() dans la BCL:

La méthode AddMonths calcule le mois et l'année résultants, en tenant compte des années bissextiles et du nombre de jours dans un mois, puis ajuste la partie jour de l'objet DateTime résultant. Si le jour résultant n'est pas un jour valide dans le mois résultant, le dernier jour valide du mois résultant est utilisé. Par exemple, 31 mars + 1 mois = 30 avril. La partie de l'heure de l'objet DateTime résultant reste la même que cette instance.

Il n'y a aucun moyen d'exprimer cela en utilisant uniquement le nom et la signature de la méthode! Bien sûr, la documentation de votre classe peut ne pas nécessiter ce niveau de détail, ce n'est qu'un exemple.


Les commentaires en ligne ne sont pas mauvais non plus.

Les mauvais commentaires sont mauvais. Par exemple des commentaires qui expliquent seulement ce qui peut être vu trivialement du code, l'exemple classique étant:

// increment x by one
x++;

Les commentaires qui expliquent quelque chose qui pourrait être clarifié en renommant une variable ou une méthode ou en restructurant autrement le code, sont une odeur de code:

// data1 is the collection of tasks which failed during execution
var data1 = getData1();

Voici le genre de commentaires Martin Rails contre. Le commentaire est un symptôme d'un échec à écrire du code clair - dans ce cas, à utiliser des noms explicites pour les variables et les méthodes. Le commentaire lui-même est bien sûr pas le problème, le problème est que nous avons besoin du commentaire pour comprendre le code.

Mais les commentaires devraient être utilisés pour expliquer tout ce qui n'est pas évident dans le code, par exemple pourquoi le code est écrit d'une certaine manière non évidente:

// need to reset foo before calling bar due to a bug in the foo component.
foo.reset()
foo.bar();

Les commentaires qui expliquent ce que fait un morceau de code trop alambiqué est aussi une odeur, mais le correctif n'est pas de proscrire les commentaires, le correctif est le correctif du code! Dans le vrai Word, du code alambiqué se produit (espérons-le seulement temporairement jusqu'à un refactor) mais aucun développeur ordinaire n'écrit du code propre parfait la première fois. Lorsque du code alambiqué se produit, il est préférable d'écrire un commentaire expliquant ce qu'il fait que pas écrire un commentaire. Ce commentaire facilitera également la refactorisation plus tard.

Parfois, le code est inévitablement complexe. Il peut s'agir d'un algorithme compliqué ou de sacrifier la clarté du code pour des raisons de performances. Encore une fois, des commentaires sont nécessaires.

102
JacquesB

Il y a une différence entre commenter votre code et documenter votre code.

  • Commentaires sont nécessaires pour maintenir le code plus tard, c'est-à-dire changer le code lui-même.

    Les commentaires peuvent en effet être perçus comme problématiques. Le point extrême serait de dire qu'ils indiquent toujours un problème, soit dans votre code (code trop difficile à comprendre) soit dans la langue (langue incapable pour être suffisamment expressif; par exemple, le fait que la méthode ne retourne jamais null pourrait être exprimé via des contrats de code en C #, mais il n'y a aucun moyen de l'exprimer via du code en, par exemple, PHP).

  • Documentation est nécessaire pour pouvoir utiliser les objets (classes, interfaces) que vous avez développés. Le public cible est différent: ce ne sont pas les personnes qui vont maintenir votre code et le changer dont nous parlons ici, mais les personnes qui ont à peine besoin d'utiliser il.

    Supprimer la documentation parce que le code est suffisamment clair est insensé, car la documentation est ici spécifiquement pour permettre d'utiliser des classes et des interfaces sans avoir à lire des milliers de lignes de code.

36
Arseni Mourzenko

Eh bien, il semblerait que votre collègue lit des livres, prenne en compte ce qu'ils disent et applique ce qu'il a appris sans réfléchir et sans tenir compte du contexte.

Votre commentaire sur ce que fait la fonction doit être tel que vous puissiez jeter le code d'implémentation, j'ai lu le commentaire de la fonction et je peux écrire le remplacement de l'implémentation, sans que rien ne se passe mal.

Si le commentaire ne me dit pas si une exception est levée ou si aucun est retourné, je ne peux pas le faire. De plus, si le commentaire ne dit pas que vous si une exception est levée ou si nil est retourné, alors où que vous appeliez la fonction, vous devez vous assurer votre code fonctionne correctement si une exception est levée ou si nil est renvoyé.

Votre collègue a donc tout à fait tort. Et allez-y et lisez tous les livres, mais pensez par vous-même.

PS. J'ai vu votre ligne "parfois, cela fait partie d'une interface, donc vous ne savez même pas quel code est en cours d'exécution." Même juste avec des fonctions virtuelles, vous ne savez pas quel code est en cours d'exécution. Pire, si vous avez écrit une classe abstraite, il n'y a même pas de code! Donc, si vous avez une classe abstraite avec une fonction abstraite, les commentaires que vous ajoutez à la fonction abstraite sont la chose seulement qu'un implémenteur d'une classe concrète doit les guider. Ces commentaires peuvent également être la seule chose qui peut guider un utilisateur de la classe, par exemple si tout ce que vous avez est une classe abstraite et une usine renvoyant une implémentation concrète, mais vous ne voyez jamais de code source de l'implémentation. (Et bien sûr, je ne devrais pas regarder le code source de l'implémentation one).

13
gnasher729

Il existe deux types de commentaires à prendre en compte: ceux visibles par les personnes possédant le code et ceux utilisés pour générer de la documentation.

Le type de commentaire auquel oncle Bob fait référence est le type qui n'est visible que par les personnes disposant du code. Ce qu'il préconise, c'est une forme de SEC . Pour une personne qui regarde le code source, le code source doit être la documentation dont elle a besoin. Même dans le cas où les gens ont accès au code source, les commentaires ne sont pas toujours mauvais. Parfois, les algorithmes sont compliqués ou vous devez saisir pourquoi vous adoptez une approche non évidente afin que d'autres ne finissent pas par casser votre code s'ils essaient de corriger un bogue ou d'ajouter une nouvelle fonctionnalité.

Les commentaires que vous décrivez sont de la documentation API. Ce sont des choses qui sont visibles pour les personnes utilisant votre implémentation, mais qui peuvent ne pas avoir accès à votre code source. Même s'ils ont accès à votre code source, ils peuvent travailler sur d'autres modules et ne pas regarder votre code source. Ces personnes trouveraient utile d'avoir cette documentation disponible dans leur IDE pendant qu'ils écrivent leur code.

12
Thomas Owens

La valeur d'un commentaire est mesurée par la valeur des informations qu'il donne moins l'effort nécessaire pour le lire et/ou l'ignorer. Donc, si nous analysons le commentaire

/// <summary>
/// Retrieves a product by its id, returns null if no product was found.
/// </summary>

pour la valeur et le coût, nous voyons trois choses:

  1. Retrieves a product by its id répète ce que dit le nom de la fonction, donc c'est un coût sans valeur. Il devrait être supprimé.

  2. returns null if no product was found est une information très précieuse. Cela réduit probablement le temps que d'autres codeurs devront examiner la mise en œuvre de la fonction. Je suis tout à fait certain qu'il économise plus de lecture que le coût de lecture qu'il se présente. Cela devrait rester.

  3. Les lignes

    /// <summary>
    /// </summary>
    

    ne porter aucune information que ce soit. Ils coûtent purement et simplement au lecteur le commentaire. Ils peuvent être justifiés si votre générateur de documentation en a besoin, mais dans ce cas, vous devriez probablement penser à un générateur de documentation différent.

    C'est la raison pour laquelle l'utilisation de générateurs de documentation est une idée contestable: ils nécessitent généralement beaucoup de commentaires supplémentaires qui ne contiennent aucune information ou répètent des choses évidentes, juste pour le bien d'un document poli.


Une observation que je n'ai trouvée dans aucune des autres réponses:

Même les commentaires pas nécessaires pour comprendre/utiliser le code peuvent être très précieux. En voici un exemple:

//XXX: The obvious way to do this would have been ...
//     However, since we need this functionality primarily for ...
//     doing this the non-obvious way of ...
//     gives us the advantage of ...

Probablement beaucoup de texte, complètement inutile pour comprendre/utiliser le code. Cependant, il explique une raison pour laquelle le code ressemble à cela. Cela empêchera les gens de regarder le code, se demander pourquoi cela n'est pas fait de manière évidente et commencerait à refactoriser le code jusqu'à ce qu'ils réalisent pourquoi le code a été écrit comme ça en premier lieu. Et même si le lecteur est suffisamment intelligent pour ne pas passer directement à la refactorisation, il doit toujours comprendre pourquoi le code ressemble à ce qu'il est avant de se rendre compte qu'il devrait mieux rester tel qu'il est. Ce commentaire pourrait littéralement économiser des heures de travail. Ainsi, la valeur est supérieure au coût.

De même, les commentaires peuvent communiquer les intentions d'un code, plutôt que son fonctionnement. Et ils peuvent peindre la grande image qui est généralement perdue dans les moindres détails du code lui-même. En tant que tel, vous avez raison d'être en faveur des commentaires de classe. J'apprécie le plus les commentaires de classe s'ils expliquent l'intention de la classe, comment elle interagit avec les autres classes, comment elle est censée être utilisée, etc. Hélas, je ne suis pas un grand auteur de tels commentaires ...

Le code non commenté est un mauvais code. C'est un mythe répandu (sinon universel) que le code peut être lu de la même manière que, disons, l'anglais. Il doit être interprété, et pour tout code, sauf le plus trivial, qui demande du temps et des efforts. De plus, tout le monde a un niveau différent de capacité à lire et à écrire une langue donnée. Les différences entre les styles et les capacités de codage de l'auteur et du lecteur sont de puissants obstacles à une interprétation précise. C'est aussi un mythe que vous pouvez tirer l'intention de l'auteur de la mise en œuvre du code. D'après mon expérience, il est rarement faux d'ajouter des commentaires supplémentaires.

Robert Martin et coll. considérez cela comme une "mauvaise odeur" car il se peut que le code soit modifié et non les commentaires. Je dis que c'est une bonne chose (exactement de la même manière qu'une "mauvaise odeur" est ajoutée au gaz domestique pour alerter l'utilisateur des fuites). La lecture des commentaires vous donne un point de départ utile pour interpréter le code réel. S'ils correspondent, vous aurez une confiance accrue dans le code. S'ils diffèrent, vous avez détecté une odeur d'avertissement et devez enquêter plus avant. Le remède à une "mauvaise odeur" n'est pas d'éliminer l'odeur mais de colmater la fuite.

4
Vince O'Sullivan

Dans certaines langues (F # par exemple), l'ensemble de ce commentaire/documentation peut en fait être exprimé dans la signature de la méthode. En effet, en F #, null n'est généralement pas une valeur autorisée, sauf autorisation spécifique.

Ce qui est courant en F # (et aussi dans plusieurs autres langages fonctionnels), c'est qu'au lieu de null, vous utilisez un type d'option Option<T> Qui pourrait être None ou Some(T). La langue comprendrait alors cela et vous forcerait à (ou vous avertir si vous ne le faites pas) correspondre dans les deux cas lorsque vous essayez de l'utiliser.

Ainsi, par exemple en F #, vous pourriez avoir une signature qui ressemble à ceci

val GetProductById: int -> Option<Product>

Et alors ce serait une fonction qui prend un paramètre (un int) puis retourne un produit ou la valeur None.

Et puis vous pouvez l'utiliser comme ça

let product = GetProduct 42
match product with
| None -> printfn "No product found!"
| Some p -> DoThingWithProduct p

Et vous obtiendrez des avertissements du compilateur si vous ne correspondez pas aux deux cas possibles. Il n'y a donc aucun moyen d'obtenir des exceptions de référence nulles (sauf si vous ignorez les avertissements du compilateur bien sûr), et vous savez tout ce dont vous avez besoin en regardant la signature de la fonction.

Bien sûr, cela nécessite que votre langage soit conçu de cette manière - ce que de nombreux langages courants tels que C #, Java, C++, etc. ne sont pas. Cela peut donc ne pas vous être utile dans votre situation actuelle. Mais c'est (espérons-le) agréable de savoir qu'il existe des langues qui vous permettent d'exprimer ce type d'informations de manière statique sans recourir aux commentaires, etc. :)

3
wasatz

Il y a d'excellentes réponses ici et je ne veux pas répéter ce qu'elles disent. Mais permettez-moi d'ajouter quelques commentaires. (Sans jeu de mots.)

Il y a beaucoup de déclarations que les gens intelligents font - à propos du développement de logiciels et de nombreux autres sujets, je suppose - qui sont de très bons conseils lorsqu'ils sont compris dans le contexte, mais qui sont stupides que de sortir de leur contexte ou d'appliquer dans des situations inappropriées ou de prendre extrêmes ridicules.

L'idée que le code devrait être auto-documenté est une excellente idée. Mais dans la vraie vie, il y a des limites à l'aspect pratique de cette idée.

Un inconvénient est que le langage peut ne pas fournir de fonctionnalités pour documenter ce qui doit être documenté de manière claire et concise. À mesure que les langages informatiques s'améliorent, cela devient de moins en moins un problème. Mais je ne pense pas que cela ait complètement disparu. À l'époque où j'écrivais dans l'assembleur, il était très logique d'inclure un commentaire comme "prix total = prix des articles non taxables plus prix des articles taxables plus prix des articles taxables * taux de taxe". Ce qui était exactement dans un registre à un moment donné n'était pas nécessairement évident. Il a fallu de nombreuses étapes pour effectuer une opération simple. Etc. Mais si vous écrivez dans un langage moderne, un tel commentaire ne serait que le retraitement d'une ligne de code.

Je suis toujours ennuyé quand je vois des commentaires comme "x = x + 7; // ajouter 7 à x". Comme, wow, merci, si j'avais oublié ce que signifie un signe plus qui aurait pu être très utile. Là où je pourrais vraiment être confus, c'est de savoir ce qu'est "x" ou pourquoi il fallait y ajouter 7 à ce moment particulier. Ce code peut être rendu auto-documenté en donnant un nom plus significatif à "x" et en utilisant une constante symbolique au lieu de 7. Comme si vous aviez écrit "total_price = total_price + MEMBERSHIP_FEE;", alors un commentaire n'est probablement pas nécessaire du tout .

Cela sonne bien de dire qu'un nom de fonction devrait vous dire exactement ce qu'une fonction fait pour que tout commentaire supplémentaire soit redondant. J'ai de bons souvenirs de l'époque où j'ai écrit une fonction qui vérifiait si un numéro d'article était dans notre base de données Table d'articles, renvoyant vrai ou faux, et que j'ai appelé, "ValidateItemNumber". Semblait comme un nom décent. Ensuite, quelqu'un d'autre est venu et a modifié cette fonction pour créer également une commande pour l'article et mettre à jour la base de données, mais n'a jamais changé le nom. Maintenant, le nom était très trompeur. Cela ressemblait à une petite chose, alors qu'en réalité, il en faisait beaucoup plus. Plus tard, quelqu'un a même retiré la partie concernant la validation du numéro d'article et l'a fait ailleurs, mais n'a toujours pas changé le nom. Ainsi, même si la fonction n'avait plus rien à voir avec la validation des numéros d'élément, elle était toujours appelée ValidateItemNumber.

Mais en pratique, il est souvent impossible pour un nom de fonction de décrire complètement ce que fait la fonction sans que le nom de la fonction soit aussi long que le code qui la compose. Le nom va-t-il nous dire exactement quelles validations sont effectuées sur tous les paramètres? Que se passe-t-il dans des conditions d'exception? Expliquez toutes les ambiguïtés possibles? À un moment donné, le nom deviendrait si long qu'il deviendrait tout simplement déroutant. J'accepterais String BuildFullName (String firstname, String lastname) comme signature de fonction décente. Même si cela ne précise pas si le nom est exprimé "premier-dernier" ou "dernier, premier" ou une autre variante, ce qu'il fait si une ou les deux parties du nom sont vides, s'il impose des limites sur la longueur combinée et ce qu'il fait s'il est dépassé, et des dizaines d'autres questions détaillées que l'on pourrait poser.

1
Jay