Limpar comentários de código vs documentação de classe

81

Estou tendo algumas discussões com meus novos colegas sobre comentários. Nós dois gostamos Código Limpo , e estou perfeitamente bem com o fato de que inline comentários de código devem ser evitados e os nomes de classes e métodos devem ser usados para expressar o que eles fazem.

No entanto, sou um grande fã de adicionar resumos de pequenas classes que tentam explicar o propósito da classe e o que ela realmente representa, principalmente para que seja fácil manter o princípio de responsabilidade simples padrão. Também estou acostumado a adicionar resumos de uma linha a métodos que explica o que o método deve fazer. Um exemplo típico é o método simples

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

Estou adicionando o seguinte resumo de método

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

Acredito que o fato de o método retornar nulo deva ser documentado. Um desenvolvedor que deseja chamar um método não deve abrir meu código para ver se o método retorna nulo ou lança uma exceção. Às vezes é parte de uma interface, então o desenvolvedor nem sabe qual código subjacente está sendo executado?

No entanto, meus colegas acham que esses tipos de comentários são " cheiro de código " e que "comentários são sempre falhas" ( Robert C. Martin ).

Existe uma maneira de expressar e comunicar esses tipos de conhecimento sem adicionar comentários? Desde que eu sou um grande fã de Robert C. Martin, estou ficando um pouco confuso. Os resumos são os mesmos que comentários e, portanto, sempre falhas?

Esta não é uma questão sobre comentários in-line.

    
por Bjorn 04.06.2015 / 10:30
fonte

9 respostas

115

Como outros já disseram, há uma diferença entre comentários documentando API e comentários em linha. Do meu ponto de vista, a principal diferença é que um comentário em linha é lido ao lado do código , enquanto um comentário da documentação é lido ao lado da assinatura do que você está comentando.

Por isso, podemos aplicar o mesmo princípio DRY . O comentário está dizendo a mesma coisa que a assinatura? Vamos ver o seu exemplo:

Retrieves a product by its id

Esta parte apenas repete o que já vemos no nome GetById mais o tipo de retorno Product . Também levanta a questão de qual é a diferença entre "obter" e "recuperar", e o que o código versus comentário tem sobre essa distinção. Então é desnecessário e um pouco confuso. Se alguma coisa, está ficando no caminho da segunda parte realmente útil do comentário:

returns null if no product was found.

Ah! Isso é algo que definitivamente não podemos ter certeza apenas da assinatura e fornece informações úteis.

Agora, leve isso um passo adiante. Quando as pessoas falam sobre comentários como cheiros de código, a questão não é se o código como é precisa de um comentário, mas se o comentário indica que o código poderia ser escrito melhor, para expressar as informações no Comente. Isso é o que significa "cheiro de código" - não significa "não faça isso!", Significa "se você está fazendo isso, pode ser um sinal de que há um problema".

Então, se seus colegas lhe disserem que esse comentário sobre null é um cheiro de código, você deve simplesmente perguntar a eles: "Ok, como devo expressar isso então?" Se eles tiverem uma resposta viável, você aprendeu alguma coisa. Se não, provavelmente vai matar suas queixas mortas.

Em relação a este caso específico, geralmente a questão nula é bem conhecida por ser difícil. Há uma base de código de razão que está repleta de cláusulas de guarda, por que as verificações de nulos são uma pré-condição popular para contratos de código, por que a existência de nulo foi chamada de "erro de bilhões de dólares". Não há muitas opções viáveis. Um popular, no entanto, encontrado em C # é a convenção Try... :

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

Em outros idiomas, pode ser idiomático usar um tipo (geralmente chamado de Optional ou Maybe ) para indicar um resultado que pode ou não estar lá:

public Optional<Product> GetById(int productId);

Então, de certa forma, essa postura antiesclarismo nos levou a algum lugar: pelo menos pensamos se esse comentário representa um cheiro e quais alternativas podem existir para nós.

Se devemos na verdade preferir estes ao longo da assinatura original é um outro debate, mas pelo menos temos opções para expressar através de código em vez de comentários o que acontece quando nenhum produto é encontrado. Você deve discutir com seus colegas qual dessas opções eles acham que é melhor e por quê, e esperamos que ajude a seguir além das afirmações dogmáticas sobre os comentários.

    
por 04.06.2015 / 11:24
fonte
102

A citação de Robert C. Martin é retirada do contexto. Aqui está a citação com um pouco mais de contexto:

Nothing can be quite so helpful as a well-placed comment. Nothing can clutter up a module more than frivolous dogmatic comments. Nothing can be quite so damaging as an old crufty comment that propagates lies and misinformation.

Comments are not like Schindler's List. They are not "pure good." Indeed, comments are, at best, a necessary evil. If our programming languages were expressive enough, or if we had the talent to subtly wield those languages to express our intent, we would not need comments very much -- perhaps not at all.

The proper use of comments is to compensate for our failure to express ourself in code. Note that I used the word failure. I meant it. Comments are always failures. We must have them because we cannot always figure out how to express ourselves without them, but their use is not a cause for celebration.

So when you find yourself in a position where you need to write a comment, think it through and see whether there isn't some way to turn the tables and express yourself in code. Every time you express yourself in code, you should pat yourself on the back. Every time you write a comment, you should grimace and feel the failure of your ability of expression.

(Copiado aqui , mas o a citação original é de Código Limpo: Um Manual de Artesanato Ágil de Software

Como esta citação é reduzida em "Comentários são sempre falhas" é um bom exemplo de como algumas pessoas farão uma citação sensata fora de contexto e a transformarão em um dogma estúpido.

A documentação da API (como o javadoc) deve documentar a API para que o usuário possa usá-la sem precisar ler o código-fonte. Portanto, neste caso, a documentação deve explicar o que o método faz. Agora você pode argumentar que "Recupera um produto pelo seu id" é redundante porque já é indicado pelo nome do método, mas as informações que null podem ser retornadas são definitivamente importantes para documentar, já que isso não é óbvio.

Se você quiser evitar a necessidade do comentário, é necessário remover o problema subjacente (que é o uso de null como um valor de retorno válido), tornando a API mais explícita. Por exemplo, você poderia retornar algum tipo de Option<Product> , então a própria assinatura do tipo comunica claramente o que será retornado caso o produto não seja encontrado.

Mas, em qualquer caso, não é realista documentar uma API totalmente apenas por meio de nomes de métodos e assinaturas de tipos. Use doc-comments para qualquer informação adicional não óbvia que o usuário deve saber. Tome digamos a documentação da API de DateTime.AddMonths() na BCL:

The AddMonths method calculates the resulting month and year, taking into account leap years and the number of days in a month, then adjusts the day part of the resulting DateTime object. If the resulting day is not a valid day in the resulting month, the last valid day of the resulting month is used. For example, March 31st + 1 month = April 30th. The time-of-day part of the resulting DateTime object remains the same as this instance.

Não há como expressar isso usando apenas o nome e a assinatura do método! É claro que sua documentação de classe pode não exigir esse nível de detalhe, é apenas um exemplo.

Comentários embutidos também não são ruins.

Os comentários ruins são ruins. Por exemplo, comentários que explicam apenas o que pode ser visto trivialmente a partir do código, sendo o exemplo clássico:

// increment x by one
x++;

Os comentários que explicam algo que poderia ser esclarecido ao renomear uma variável ou método ou reestruturar o código de outra forma, são um cheiro de código:

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

Estes são os tipos de comentários contra os quais Martin se queixa. O comentário é um sintoma de uma falha ao escrever código claro - neste caso, usar nomes auto-explicativos para variáveis e métodos. O comentário em si não é, claro, o problema, o problema é que precisamos do comentário para entender o código.

Mas os comentários devem ser usados para explicar tudo o que não é óbvio no código, por exemplo por que o código foi escrito de uma maneira não óbvia:

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

Comentários que explicam o que um código excessivamente complicado faz também é um cheiro, mas a correção não é proibir comentários, a correção é a correção do código! Na palavra real, o código complicado acontece (esperamos apenas temporariamente até um refatorador), mas nenhum desenvolvedor comum escreve código limpo perfeito na primeira vez. Quando um código complicado acontece, é muito melhor escrever um comentário explicando o que ele faz do que não escrever um comentário. Este comentário também facilitará a refatoração posterior.

Às vezes, o código é inevitavelmente complexo. Pode ser um algoritmo complicado, ou pode ser código sacrificando a clareza por motivos de desempenho. Mais uma vez os comentários são necessários.

    
por 04.06.2015 / 10:55
fonte
35

Existe uma diferença entre comentar o seu código e documentar o seu código .

  • Comentários são necessários para manter o código mais tarde, ou seja, alterar o próprio código.

    Os comentários podem, de fato, ser percebidos como problemáticos. O ponto extremo seria dizer que eles sempre indicam um problema, seja dentro do seu código (código muito difícil de entender) ou dentro da linguagem (linguagem incapaz de ser expressiva o suficiente; por exemplo, o fato de o método nunca retorna null poderia ser expresso através de contratos de código em C #, mas não há como expressá-lo através de código, digamos, PHP).

  • Documentação é necessária para poder usar os objetos (classes, interfaces) que você desenvolveu. O público-alvo é diferente: não são as pessoas que manterão seu código e o alterarão de que estamos falando aqui, mas as pessoas que mal precisam usá-lo .

    Remover a documentação porque o código é claro o suficiente é insano, pois a documentação está aqui especificamente para possibilitar o uso de classes e interfaces sem ter que ler milhares de linhas de código .

por 04.06.2015 / 11:09
fonte
13

Bem, parece que seu colega lê livros, aceita o que eles dizem e aplica o que aprendeu sem pensar e sem considerar o contexto.

Seu comentário sobre o que a função faz deve ser tal que você pode jogar fora o código de implementação, eu li o comentário da função, e eu posso escrever o substituto para a implementação, sem que nada dê errado.

Se o comentário não me disser se uma exceção é lançada ou se nil é retornado, não posso fazer isso. Além disso, se o comentário não disser você se uma exceção é lançada ou se nil é retornado, então, onde quer que você chame a função, você deve certificar-se de que seu código funcione corretamente se uma exceção for lançada ou nula é retornado.

Então, seu colega está totalmente errado. E vá em frente e leia todos os livros, mas pense por si mesmo.

PS. Eu vi sua linha "às vezes é parte de uma interface, então você nem sabe qual código está sendo executado". Mesmo apenas com funções virtuais, você não sabe qual código está sendo executado. Pior, se você escreveu uma aula abstrata, não existe código! Então, se você tem uma classe abstrata com uma função abstrata, os comentários que você adiciona à função abstrata são a única coisa somente que um implementador de uma classe concreta tem para guiá-los. Esses comentários também podem ser a única coisa que pode guiar um usuário da classe, por exemplo, se tudo que você tem é uma classe abstrata e uma fábrica retornando uma implementação concreta, mas você nunca vê nenhum código-fonte da implementação. (E é claro que eu não deveria estar olhando o código-fonte da uma implementação).

    
por 04.06.2015 / 10:56
fonte
12

Existem dois tipos de comentários a serem considerados - aqueles visíveis para as pessoas com o código e aqueles que são usados para gerar documentação.

O tipo de comentário ao qual o Uncle Bob está se referindo é do tipo que só é visível para as pessoas com o código. O que ele defende é uma forma de DRY . Para uma pessoa que está olhando para o código-fonte, o código-fonte deve ser a documentação que eles precisam. Mesmo no caso em que as pessoas têm acesso ao código-fonte, os comentários nem sempre são ruins. Às vezes, os algoritmos são complicados ou você precisa capturar por que está adotando uma abordagem não óbvia para que os outros não acabem quebrando seu código se tentarem corrigir um bug ou adicionar um novo recurso.

Os comentários que você está descrevendo são documentação da API. Essas são coisas que são visíveis para as pessoas que usam sua implementação, mas que podem não ter acesso ao seu código-fonte. Mesmo que eles tenham acesso ao seu código-fonte, eles podem estar trabalhando em outros módulos e não estar olhando para o seu código-fonte. Essas pessoas achariam útil ter esta documentação disponível em seu IDE enquanto escrevem seu código.

    
por 04.06.2015 / 11:06
fonte
8

O valor de um comentário é medido no valor das informações fornecidas, menos o esforço necessário para lê-lo e / ou ignorá-lo. Então, se analisarmos o comentário

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

por valor e custo, vemos três coisas:

  1. Retrieves a product by its id repete o que o nome da função diz, portanto, é custo sem valor. Deve ser eliminado.

  2. returns null if no product was found é uma informação muito valiosa. É provável que reduza os tempos em que outros codificadores terão que examinar a implementação da função. Tenho certeza de que economiza mais leitura do que o custo de leitura que ela introduz. Deve ficar.

  3. As linhas

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

    não carregam informação alguma. Eles são puro custo para o leitor do comentário. Eles podem ser justificados se o seu gerador de documentação precisar deles, mas, nesse caso, você provavelmente deve pensar em um outro gerador de documentação.

    Esta é a razão pela qual o uso de geradores de documentação é uma idéia discutível: eles geralmente exigem muitos comentários adicionais que não trazem nenhuma informação ou repetem coisas óbvias, apenas por causa de um documento polido.

Uma observação que não encontrei em nenhuma das outras respostas:

Mesmo os comentários que não são necessários para entender / usar o código podem ser muito valiosos. Aqui está um exemplo:

//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 ...

Provavelmente muito texto, completamente desnecessário para entender / usar o código. No entanto, isso explica a razão pela qual o código parece da maneira como funciona. Isso irá impedir as pessoas de olharem para o código, se perguntarem por que ele não foi feito da maneira mais óbvia e começarão a refatorar o código até que percebam porque o código foi escrito assim em primeiro lugar. E mesmo que o leitor seja inteligente o suficiente para não pular para a refatoração diretamente, eles ainda precisam descobrir por que o código parece da maneira que ele faz antes de perceber que é melhor ficar do jeito que está. Este comentário poderia salvar literalmente horas de trabalho. Assim, o valor é maior que o custo.

Da mesma forma, os comentários podem comunicar as intenções de um código, em vez de apenas como ele funciona. E eles podem pintar o quadro geral que normalmente é perdido nos mínimos detalhes do próprio código. Como tal, você está certo em estar em comentários de classe favoráveis. Eu valorizo mais os comentários da classe se eles explicam a intenção da classe, como ela interage com outras classes, como ela deve ser usada, etc. Infelizmente, eu não sou um grande autor de tais comentários ...

    
por 04.06.2015 / 19:25
fonte
5

Código sem comentários é um código inválido. É um mito generalizado (se não universal) que o código pode ser lido da mesma maneira que, digamos, o inglês. Deve ser interpretado e, para qualquer um, exceto o código mais trivial, que exige tempo e esforço. Além disso, todos têm um nível diferente de capacidade em ler e escrever qualquer idioma. Diferenças entre os estilos e capacidades de codificação do autor e do leitor são strongs barreiras à interpretação precisa. Também é um mito que você possa derivar a intenção do autor da implementação do código. Na minha experiência, raramente é errado adicionar comentários extras.

Robert Martin et al. considere um "mau cheiro" porque pode ser que o código seja alterado e os comentários não. Eu digo que é uma coisa boa (exatamente da mesma maneira que um "mau cheiro" é adicionado ao gás doméstico para alertar o usuário sobre vazamentos). Ler os comentários é um ponto de partida útil para interpretar o código real. Se eles corresponderem, você terá mais confiança no código. Se forem diferentes, você detectou um cheiro de aviso e precisa investigar mais. A cura para um "mau cheiro" não é para remover o cheiro, mas para selar o vazamento.

    
por 04.06.2015 / 13:43
fonte
3

Em alguns idiomas (por exemplo, em F #) todo este comentário / documentação pode realmente ser expresso na assinatura do método. Isso ocorre porque, em F #, nulo geralmente não é um valor permitido, a menos que seja especificamente permitido.

O que é comum em F # (e também em várias outras linguagens funcionais) é que, em vez de null, você usa um tipo de opção Option<T> , que pode ser None ou Some(T) . A linguagem então entenderia isso e forçaria você a (ou avisá-lo se você não o fizer) em ambos os casos quando você tentar usá-lo.

Então, por exemplo, em F # você poderia ter uma assinatura parecida com esta

val GetProductById: int -> Option<Product>

E então, essa seria uma função que usa um parâmetro (um int) e retorna um produto, ou o valor Nenhum.

E então você poderia usá-lo assim

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

E você receberia avisos do compilador se não corresponder aos dois casos possíveis. Portanto, não há nenhuma maneira possível de obter exceções de referência nula lá (a menos que você ignore os avisos do compilador, é claro) e você sabe tudo o que precisa apenas observando a assinatura da função.

Claro, isso requer que sua linguagem seja projetada dessa forma - o que muitas linguagens comuns, como C #, Java, C ++, etc. não são. Então, isso pode não ser útil para você em sua situação atual. Mas é (espero) legal saber que existem idiomas por aí que permitem expressar esse tipo de informação de maneira estaticamente tipificada sem recorrer a comentários, etc.:)

    
por 04.06.2015 / 15:39
fonte
1

Há algumas respostas excelentes aqui e não quero repetir o que dizem. Mas deixe-me adicionar alguns comentários. (Sem trocadilhos).

Há muitas declarações que pessoas inteligentes fazem - sobre desenvolvimento de software e muitos outros assuntos, suponho - que são conselhos muito bons quando entendidos em contexto, mas que pessoas bobas tiram de contexto ou aplicam em situações inapropriadas ou levar a extremos ridículos.

A ideia de que o código deve ser auto-documentado é uma excelente ideia. Mas na vida real, há limitações na praticidade dessa ideia.

Um problema é que o idioma pode não fornecer recursos para documentar o que precisa ser documentado de forma clara e concisa. À medida que as linguagens de computador melhoram, isso se torna cada vez menos um problema. Mas não acho que tenha desaparecido completamente. Nos dias em que escrevi em assembler, fazia muito sentido incluir um comentário como "preço total = preço de itens não tributáveis mais preço de itens tributáveis mais preço de itens tributáveis * taxa de imposto". Exatamente o que estava em um registro em um dado momento não era necessariamente óbvio. Foram necessários muitos passos para fazer uma operação simples. Etc. Mas se você está escrevendo em uma linguagem moderna, tal comentário seria apenas uma reafirmação de uma linha de código.

Eu sempre fico irritado quando vejo comentários como "x = x + 7; // adiciona 7 a x". Como, uau, obrigado, se eu tivesse esquecido o que significa um sinal de mais que poderia ter sido muito útil. Onde eu realmente posso estar confuso é em saber o que "x" é ou porque foi necessário adicionar 7 a ele neste momento específico. Este código pode ser auto-documentado dando um nome mais significativo a "x" e usando uma constante simbólica em vez de 7. Como se você tivesse escrito "total_price = total_price + MEMBERSHIP_FEE;", então um comentário provavelmente não é necessário .

Parece ótimo dizer que um nome de função deve dizer exatamente o que uma função faz para que quaisquer comentários adicionais sejam redundantes. Tenho boas lembranças da época em que escrevi uma função que verificava se um número de item estava em nossa tabela Item, retornando verdadeiro ou falso, e que eu chamei de "ValidateItemNumber". Parecia um nome decente. Então alguém apareceu e modificou essa função para também criar uma ordem para o item e atualizar o banco de dados, mas nunca mudou o nome. Agora o nome era muito enganador. Parecia que fazia uma coisa pequena, quando na verdade fazia muito mais. Mais tarde, alguém tirou a parte sobre a validação do número do item e fez isso em outro lugar, mas ainda não alterou o nome. Portanto, embora a função agora não tivesse nada a ver com a validação de números de itens, ela ainda era chamada de ValidateItemNumber.

Mas, na prática, muitas vezes é impossível para um nome de função descrever completamente o que a função faz sem que o nome da função seja tão longo quanto o código que compõe essa função. O nome vai nos dizer exatamente quais validações são realizadas em todos os parâmetros? O que acontece em condições de exceção? Soletrar todas as ambiguidades possíveis? Em algum momento, o nome ficaria tão longo que só se tornaria confuso. Eu aceitaria String BuildFullName (nome da cadeia, sobrenome da seqüência) como uma assinatura de função decente. Mesmo que isso não explique se o nome é expresso "primeiro por último" ou "último, primeiro" ou alguma outra variação, o que acontece se uma ou ambas as partes do nome estiverem em branco, se impuser limites no comprimento combinado e o que acontece se isso for excedido, e dezenas de outras perguntas detalhadas podem ser feitas.

    
por 05.06.2015 / 16:23
fonte