Estou tendo algumas discussões com meus novos colegas sobre comentários. Nós dois gostamos de Clean Code , e estou perfeitamente bem com o fato de que os comentários de código em linha devem ser evitados e que os nomes de classe e métodos devem ser usados para expressar o que eles fazem.
No entanto, sou um grande fã de adicionar pequenos resumos de classe que tentam explicar o objetivo da classe e o que realmente representa, principalmente para que seja fácil manter o padrão de princípio de responsabilidade única . Também estou acostumado a adicionar resumos de uma linha a métodos que explicam 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 do 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 deve ser documentado. Um desenvolvedor que deseja chamar um método não precisa abrir meu código para ver se o método retorna nulo ou gera uma exceção. Às vezes, faz parte de uma interface, para que o desenvolvedor nem saiba qual código subjacente está sendo executado?
No entanto, meus colegas pensam 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? Como sou um grande fã de Robert C. Martin, estou ficando um pouco confuso. Os resumos são iguais aos comentários e, portanto, sempre falham?
Esta não é uma pergunta sobre comentários em linha.
fonte
Respostas:
Como outros já disseram, há uma diferença entre comentários de documentação da API e comentários in-line. Na minha perspectiva, a principal diferença é que um comentário in-line é lido junto com o código , enquanto um comentário de documentação é lido junto com a assinatura do que você estiver comentando.
Diante disso, podemos aplicar o mesmo princípio DRY . O comentário está dizendo a mesma coisa que a assinatura? Vejamos o seu exemplo:
Esta parte apenas repete o que já vemos no nome
GetById
mais o tipo de retornoProduct
. Também levanta a questão de saber qual é a diferença entre "obter" e "recuperar" e qual o significado do código versus comentário nessa distinção. Portanto, é desnecessário e um pouco confuso. Na verdade, está atrapalhando a segunda parte realmente útil do comentário:Ah! Isso é algo que definitivamente não podemos saber ao certo apenas da assinatura e fornece informações úteis.
Agora dê um passo adiante. Quando as pessoas falam sobre comentários com o cheiro do código, a questão não é se o código atual precisa de um comentário, mas se o comentário indica que o código poderia ser melhor escrito, para expressar as informações no comentário. É isso que "cheiro de código" significa - não significa "não faça isso!", Significa "se você estiver fazendo isso, pode ser um sinal de que há um problema".
Portanto, se seus colegas lhe disserem que esse comentário sobre nulo é um cheiro de código, você deve simplesmente perguntar a eles: "Ok, como devo expressar isso?" Se eles têm uma resposta viável, você aprendeu alguma coisa. Se não, provavelmente matará as queixas deles.
Em relação a esse caso específico, geralmente o problema nulo é bem conhecido por ser difícil. Há uma razão pela qual as bases de código estão repletas de cláusulas de guarda, por que as verificações nulas são uma pré-condição popular para contratos de código, por que a existência de nula foi chamada de "erro de bilhão de dólares". Não existem muitas opções viáveis. Um popular, porém, encontrado em C # é a
Try...
convenção:Em outros idiomas, pode ser idiomático usar um tipo (geralmente chamado de algo como
Optional
ouMaybe
) para indicar um resultado que pode ou não estar lá:De certa forma, essa postura anti-comentário 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 realmente preferir isso à assinatura original, existe outro debate, mas pelo menos temos opções para expressar o código em vez de comentar o que acontece quando nenhum produto é encontrado. Você deve discutir com seus colegas quais dessas opções eles acham que é melhor e por quê e, espero, ajudar a seguir além das declarações dogmáticas gerais sobre comentários.
fonte
Linq
-equivalente deTry...
,...OrDefault
que voltardefault(T)
se a cláusula levaria a um resultado vazio.TryGetValue
padrão é uma maneira razoável de fazer isso em C #, mas a maioria das linguagens funcionais tem uma maneira melhor de representar um valor ausente. Leia mais aquiT TryGetValue(params, ref bool success)
para qualquer tipo T ouT TryGetValue(params)
, com nulo indicando falha, para o tipo T com restrição de classe, mas oTryGetXX
padrão retornadobool
é incompatível com covariância.Optional<Product>
para indicar que pode não haver um Produto retornado do método.As citações de Robert C. Martin são retiradas do contexto. Aqui está a citação com um pouco mais de contexto:
(Copiado daqui , mas a citação original é de Código Limpo: Um Manual do Artesanato em Software Ágil )
Como essa citação é reduzida em "Comentários sempre são falhas" é um bom exemplo de como algumas pessoas tiram uma citação sensata do contexto e a transformam em dogma estúpido.
A documentação da API (como 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 por seu ID" é redundante porque já está indicado pelo nome do método, mas as informações que
null
podem ser retornadas são definitivamente importantes para o documento, pois isso não é de forma alguma óbvio.Se você deseja evitar a necessidade do comentário, é necessário remover o problema subjacente (que é o uso
null
como um valor de retorno válido), tornando a API mais explícita. Por exemplo, você pode retornar algum tipo deOption<Product>
tipo, para que a própria assinatura de tipo comunique claramente o que será retornado caso o produto não seja encontrado.Mas, em qualquer caso, não é realista documentar completamente uma API apenas por meio de nomes de métodos e assinaturas de tipo. Use doc-comments para qualquer informação adicional não óbvia que o usuário deva conhecer. Imagine a documentação da API
DateTime.AddMonths()
na BCL:Não há como você expressar isso usando apenas o nome e a assinatura do método! É claro que a documentação da sua aula pode não exigir esse nível de detalhe, é apenas um exemplo.
Comentários embutidos também não são ruins.
Bad comentários são ruins. Por exemplo, comentários que explicam apenas o que pode ser visto trivialmente no código, sendo o exemplo clássico:
Comentários que explicam algo que poderia ser esclarecido renomeando uma variável ou método ou reestruturando o código, é um cheiro de código:
Esse é o tipo de comentário contra o qual Martin se opõe. O comentário é um sintoma de uma falha ao escrever um código claro - nesse caso, para usar nomes auto-explicativos para variáveis e métodos. O comentário em si não é, obviamente, 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 certa maneira não óbvia:
Comentários que explicam o que um código excessivamente complicado faz também são cheiros, mas a correção não é proibir comentários, a correção é a correção do código! Na palavra real, código complicado acontece (espero apenas temporariamente até um refator), mas nenhum desenvolvedor comum escreve código limpo perfeito na primeira vez. Quando o código complicado acontece, é muito melhor escrever um comentário explicando o que ele faz do que não escrever um comentário. Esse comentário também facilitará a refatoração mais tarde.
Às vezes, o código é inevitavelmente complexo. Pode ser um algoritmo complicado ou pode comprometer a clareza do código por razões de desempenho. Novamente, comentários são necessários.
fonte
x++
pode ser bom se for algo como "incrementar x por um, contornando se for UINT32_MAX"; qualquer pessoa que conheça a especificação da linguagem saberá que incrementar umuint32_t
empacotamento será, mas sem o comentário, talvez não se saiba se esse empacotamento era uma parte esperada do algoritmo sendo implementado.Há uma diferença entre comentar e documentar seu código .
Comentários são necessários para manter o código posteriormente, ou seja, altere o próprio código.
Comentários podem de fato ser percebidos como problemáticos. O ponto extremo seria dizer que eles sempre indicam um problema, seja no seu código (código muito difícil de entender) ou no idioma (idioma incapaz de ser expressivo o suficiente; por exemplo, o fato de o método nunca retornar
null
pode ser expresso através de contratos de código em C #, mas não há como expressá-lo através de código em, digamos, PHP).É necessária documentação 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 mudarão sobre o qual 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 a necessidade de ler milhares de linhas de código .
fonte
Bem, parece que seu colega lê livros, absorve 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ê possa jogar fora o código de implementação, li o comentário da função e posso escrever o substituto para a implementação, sem que algo dê errado.
Se o comentário não me informar se uma exceção é lançada ou se nada é retornado, não posso fazer isso. Além disso, se o comentário não diz que você se uma exceção é lançada ou se nulo é retornado, então onde quer que você chamar a função, você deve se certificar que seu código funciona corretamente se uma exceção é lançada ou nulo é 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 faz parte de uma interface, então você nem sabe qual código está em execução". Mesmo apenas com funções virtuais, você não sabe qual código está em execução. Pior, se você escreveu uma classe abstrata, não há nenhum código! Portanto, se você tem uma classe abstrata com uma função abstrata, os comentários adicionados à função abstrata são a única coisa que um implementador de uma classe concreta precisa para guiá-los. Esses comentários também podem ser a única coisa que pode orientar um usuário da classe, por exemplo, se tudo o 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 de uma implementação).
fonte
Existem dois tipos de comentários a serem considerados - os visíveis para as pessoas com o código e os usados para gerar documentação.
O tipo de comentário ao qual o tio Bob está se referindo é do tipo que só é visível para as pessoas com o código. O que ele está defendendo é uma forma de DRY . Para uma pessoa que está olhando para o código-fonte, o código-fonte deve ser a documentação necessária. 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 entender por que você está adotando uma abordagem não óbvia para que outras pessoas 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. São coisas 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 acham útil ter essa documentação disponível em seu IDE enquanto escrevem seu código.
fonte
O valor de um comentário é medido no valor da informação fornecida menos o esforço necessário para lê-lo e / ou ignorá-lo. Então, se analisarmos o comentário
por valor e custo, vemos três coisas:
Retrieves a product by its id
repete o que o nome da função diz, portanto, é um custo sem valor. Deve ser excluído.returns null if no product was found
é uma informação muito valiosa. Provavelmente reduz o tempo que outros codificadores terão que observar a implementação da função. Estou certo de que economiza mais leitura do que o custo de leitura que se apresenta. Deveria ficar.As linhas
não carregue nenhuma informação. Eles são um custo puro 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 outro gerador de documentação.
Essa é 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 informações ou repetem coisas óbvias, apenas por causa de um documento polido.
Uma observação que não encontrei em nenhuma das outras respostas:
Mesmo comentários que não são necessários para entender / usar o código podem ser muito valiosos. Aqui está um exemplo:
Provavelmente muito texto, completamente desnecessário para entender / usar o código. No entanto, explica uma razão pela qual o código parece com a sua aparência. Ele interrompe as pessoas que olham para o código, se perguntam por que não é feito da maneira óbvia e começam a refatorar o código até perceberem por que o código foi escrito assim em primeiro lugar. E mesmo que o leitor seja esperto o suficiente para não pular para a refatoração direta, ele ainda precisará descobrir por que o código tem a aparência que ele tem antes de perceber que ele deve permanecer do jeito que está. Esse comentário poderia literalmente economizar horas de trabalho. Assim, o valor é superior ao 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 geralmente se perde nos mínimos detalhes do próprio código. Como tal, você tem razão em ser a favor dos comentários da classe. Eu valorizo mais os comentários da classe se eles explicarem a intenção da classe, como ela interage com outras classes, como ela deve ser usada etc. etc. Infelizmente, eu não sou um grande autor de tais comentários ...
fonte
GetById
levanta a questão, o que é id e também obtém o que de onde. O comentário da documentação deve permitir que o ambiente de desenvolvimento exiba resposta a essas perguntas. A menos que seja explicado em outro lugar nos comentários do documento do módulo, também seria um lugar para dizer por que alguém obteria o ID de qualquer maneira.Código não comentado é código incorreto. É um mito generalizado (se não universal) de que o código pode ser lido da mesma maneira que, digamos, em inglês. Ele deve ser interpretado e para qualquer código, exceto o mais trivial, que leva tempo e esforço. Além disso, todo mundo tem um nível diferente de capacidade de ler e escrever qualquer idioma. As diferenças entre o autor e os estilos e recursos de codificação do leitor são fortes barreiras à interpretação precisa. Também é um mito que você pode derivar a intenção do autor a partir 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). A leitura dos comentários fornece um ponto de partida útil para a interpretação do código real. Se eles corresponderem, você terá maior confiança no código. Se eles diferem, você detectou um cheiro de aviso e precisa investigar mais. A cura para um "mau cheiro" não é remover o cheiro, mas selar o vazamento.
fonte
i++; // Adjust for leap years.
Em alguns idiomas (F # por exemplo), todo esse 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 no F # (e também em várias outras linguagens funcionais) é que, em vez de nulo, você usa um tipo de opção
Option<T>
que pode serNone
ouSome(T)
. O idioma entenderia isso e forçaria você a (ou avisará se não o fizer) corresponder nos dois casos ao tentar usá-lo.Por exemplo, em F #, você poderia ter uma assinatura parecida com esta
E isso seria uma função que usa um parâmetro (um int) e, em seguida, retorna um produto ou o valor Nenhum.
E então você poderia usá-lo assim
E você receberá avisos do compilador se não corresponder aos dois casos possíveis. Portanto, não há como obter exceções de referência nula (a menos que você ignore os avisos do compilador, é claro), e você sabe tudo o que precisa apenas olhando a assinatura da função.
Obviamente, isso requer que sua linguagem seja projetada dessa maneira - o que muitas linguagens comuns como C #, Java, C ++ etc. Portanto, isso pode não ser útil para você na sua situação atual. Mas (espero) é bom saber que existem idiomas por aí que permitem expressar esse tipo de informação de maneira estaticamente tipada, sem recorrer a comentários etc. :)
fonte
Há algumas excelentes respostas 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 tolas que saem de contexto ou se aplicam em situações inapropriadas ou levam a extremos ridículos.
A idéia de que o código deve ser auto-documentado é uma excelente ideia. Mas na vida real, existem limitações na praticidade dessa idéia.
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 problemático. Mas não acho que tenha desaparecido completamente. Na época em que escrevi na 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 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árias várias etapas para executar uma operação simples. Etc. Mas se você estiver escrevendo em uma linguagem moderna, esse comentário seria apenas uma reformulação de uma linha de código.
Sempre fico irritado quando vejo comentários como "x = x + 7; // adicione 7 a x". Como, uau, obrigado, se eu tivesse esquecido o que um sinal de mais significa que poderia ter sido muito útil. Onde eu realmente posso estar confuso é em saber o que é "x" ou por que foi necessário adicionar 7 a ele neste momento específico. Esse 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;", um comentário provavelmente não será necessário. .
Parece ótimo dizer que o nome de uma função deve informar 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 de itens do banco de dados, retornando verdadeiro ou falso, e que chamei de "ValidateItemNumber". Parecia um nome decente. Então alguém apareceu e modificou essa função para também criar um pedido 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 realmente fazia muito mais. Mais tarde, alguém chegou a validar o número do item e fez isso em outro lugar, mas ainda não mudou o nome. Portanto, mesmo que a função agora não tenha nada a ver com a validação de números de itens,
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 a compõe. O nome nos dirá exatamente quais validações são executadas 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 se tornaria confuso. Eu aceitaria String BuildFullName (String firstname, String lastname) como uma assinatura de função decente. Mesmo que isso não explique se o nome é expresso "primeiro sobrenome" ou "sobrenome, primeiro" ou alguma outra variação, o que faz se uma ou ambas as partes do nome estiverem em branco, se impuser limites ao comprimento combinado e o que faz se for excedido,
fonte