O que há de errado com comentários que explicam códigos complexos?

236

Muitas pessoas afirmam que "os comentários devem explicar 'por que', mas não 'como'". Outros dizem que "o código deve ser auto-documentado" e os comentários devem ser escassos. Robert C. Martin afirma que (reformulado com minhas próprias palavras) frequentemente "os comentários são desculpas por códigos mal escritos".

Minha pergunta é a seguinte:

O que há de errado em explicar um algoritmo complexo ou um pedaço longo e complicado de código com um comentário descritivo?

Dessa forma, em vez de outros desenvolvedores (inclusive você) terem que ler toda a linha algoritmo por linha para descobrir o que ele faz, eles podem simplesmente ler o comentário descritivo amigável que você escreveu em inglês simples.

O inglês é "projetado" para ser facilmente compreendido pelos seres humanos. Java, Ruby ou Perl, no entanto, foram projetados para equilibrar legibilidade humana e legibilidade por computador, comprometendo assim a legibilidade humana do texto. Um humano pode entender um pedaço de inglês muito mais rapidamente do que ele / ela pode entender um pedaço de código com o mesmo significado (desde que a operação não seja trivial).

Então, depois de escrever um pedaço complexo de código, escrito em uma linguagem de programação parcialmente legível por humanos, por que não adicionar um comentário descritivo e conciso que explique a operação do código em inglês amigável e compreensível?

Alguns dirão que "o código não deve ser difícil de entender", "torne as funções pequenas", "use nomes descritivos", "não escreva código espaguete".

Mas todos sabemos que isso não é suficiente. Essas são meras diretrizes - importantes e úteis -, mas não mudam o fato de que alguns algoritmos são complexos. E, portanto, é difícil de entender ao lê-los linha por linha.

É realmente ruim explicar um algoritmo complexo com algumas linhas de comentários sobre sua operação geral? O que há de errado em explicar código complicado com um comentário?

Aviv Cohn
fonte
14
Se for complicado, tente refatorá-lo em pedaços menores.
Vaughan Hilts
151
Em teoria, não há diferença entre teoria e prática. Na prática, existe.
Scott Leadley
5
@mattnz: mais diretamente, no momento em que você escreve o comentário, está imerso no problema que esse código resolve. Na próxima vez que você visitar, você terá menos recursos com esse problema .
Steve Jessop
26
"O que" a função ou método faz deve ser óbvio pelo nome. Como isso acontece, é óbvio a partir do seu código. Por que é feito dessa maneira, quais suposições implícitas foram usadas, quais documentos é preciso ler para entender o algoritmo etc. - devem estar nos comentários.
SK-logic
11
Sinto que muitas das respostas abaixo estão intencionalmente interpretando sua pergunta. Não há nada de errado em comentar seu código. Se você sente que precisa escrever um comentário explicativo, é necessário.
Tony Ennis

Respostas:

408

Em termos leigos:

  • Não há nada errado com os comentários em si. O que há de errado é escrever um código que precise desse tipo de comentário ou presumir que não há problema em escrever um código complicado, desde que você o explique de maneira amigável em inglês comum.
  • Os comentários não se atualizam automaticamente quando você altera o código. É por isso que muitas vezes os comentários não estão sincronizados com o código.
  • Os comentários não facilitam o teste do código.
  • Desculpar-se não é ruim. O que você fez e pede desculpas (escrever código que não é facilmente compreensível) é ruim.
  • Um programador capaz de escrever código simples para resolver um problema complexo é melhor do que aquele que escreve código complexo e depois escreve um longo comentário explicando o que seu código faz.

Bottom line:

Explicar-se é bom, não é melhor fazê-lo.

Tulains Córdova
fonte
91
Freqüentemente, é impossível justificar que o gasto do código de reescrita do empregador seja mais auto-explicativo, quando um bom comentário pode fazer o trabalho em muito menos tempo. Um programador obediente deve usar seu julgamento toda vez.
aecolley
34
@aecolley Para começar, escrever um código auto-explicativo é melhor ainda.
Tulains Córdova 01/09/14
127
Às vezes, o código auto-explicativo não é eficiente o suficiente para resolver um problema com o HW&SW de hoje. E a lógica de negócios é notoriamente ... distorcida. O subconjunto de problemas que possuem soluções de software elegantes é significativamente menor do que o conjunto de problemas economicamente úteis para resolver.
Scott Leadley
62
@rwong: por outro lado, muitas vezes me pego escrevendo mais comentários na lógica de negócios, porque é importante mostrar exatamente como o código está alinhado com os requisitos estabelecidos: "essa é a linha que impede todos nós de ser presos por fraude eletrônica na seção o código penal ". Se for apenas um algoritmo, bem, um programador pode descobrir o objetivo do zero, se for absolutamente necessário. Para a lógica de negócios, você precisa de um advogado e o cliente na mesma sala ao mesmo tempo. Possivelmente o meu "bom senso" está em um domínio diferente do ;-) programador médio aplicativo
Steve Jessop
29
@ user61852 Exceto que o que é autoexplicativo para você que acabou de escrever esse código e passou o último período de $ imerso nele pode não ser autoexplicativo para você que deve mantê-lo ou editá-lo daqui a cinco anos, e muito menos possíveis pessoas que não são você que podem ter que olhar para ele. "Auto-explicativo" é um santo graal nebuloso de definições.
Shadur 01/09/14
110

Há várias razões diferentes para o código ser complicado ou confuso. Os motivos mais comuns são melhor resolvidos refatorando o código para torná-lo menos confuso, não adicionando comentários de qualquer tipo.

No entanto, há casos em que um comentário bem escolhido é a melhor escolha.

  • Se é o próprio algoritmo que é complicado e confuso, não apenas a sua implementação - o tipo que é escrito em periódicos de matemática e é conhecido como Algoritmo de Mbogo - você coloca um comentário no início da implementação, lendo algo como "Este é o algoritmo do Mbogo para atualizar widgets, originalmente descrito aqui: [URL do artigo]. Esta implementação contém aprimoramentos de Alice e Carol [URL de outro artigo]". Não tente entrar em mais detalhes do que isso; se alguém precisar de mais detalhes, provavelmente precisará ler o artigo inteiro.

  • Se você pegou algo que pode ser escrito como uma ou duas linhas em alguma notação especializada e a expandiu para uma grande quantidade de códigos imperativos, colocar essas uma ou duas linhas de notação especializada em um comentário acima da função é uma boa maneira de diga ao leitor o que ele deve fazer. Isso é uma exceção ao argumento "mas e se o comentário ficar fora de sincronia com o código", porque a notação especializada é provavelmente muito mais fácil de encontrar bugs do que o código. (É o contrário, se você escreveu uma especificação em inglês.) Um bom exemplo é o seguinte: https://dxr.mozilla.org/mozilla-central/source/layout/style/nsCSSScanner.cpp#1057 ...

    /**
     * Scan a unicode-range token.  These match the regular expression
     *
     *     u\+[0-9a-f?]{1,6}(-[0-9a-f]{1,6})?
     *
     * However, some such tokens are "invalid".  There are three valid forms:
     *
     *     u+[0-9a-f]{x}              1 <= x <= 6
     *     u+[0-9a-f]{x}\?{y}         1 <= x+y <= 6
     *     u+[0-9a-f]{x}-[0-9a-f]{y}  1 <= x <= 6, 1 <= y <= 6
    
  • Se o código for direto no geral, mas contiver uma ou duas coisas que parecem excessivamente complicadas, desnecessárias ou simplesmente erradas, mas precisam ser assim por motivos, então você coloca um comentário imediatamente acima da parte suspeita, na qual você declara os motivos . Aqui está um exemplo simples, onde a única coisa que precisa ser explicada é por que uma constante tem um certo valor.

    /* s1*s2 <= SIZE_MAX if s1 < K and s2 < K, where K = sqrt(SIZE_MAX+1) */
    const size_t MUL_NO_OVERFLOW = ((size_t)1) << (sizeof(size_t) * 4);
    if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
        nmemb > 0 && SIZE_MAX / nmemb < size)
      abort();
    
zwol
fonte
25
Isso é um ultraje, 4deve ser CHAR_BIT / 2;-)
Steve Jessop
@SteveJessop: Alguma coisa impediria uma implementação de CHAR_BITS16 e sizeof (size_t) 2, mas o valor máximo de size_t era, por exemplo, 2 ^ 20 [size_t contendo 12 bits de preenchimento]?
Supercat 02/09
2
@ supercat Não vejo nada que obviamente impeça isso no C99, o que significa que o exemplo é tecnicamente incorreto. Acontece a ser tomadas a partir de (uma versão ligeiramente modificada do) OpenBSD de reallocarraye OpenBSD geralmente não acreditam na restauração de possibilidades que não acontecem em sua ABI.
Zwol
3
@Zack: Se o código é projetado em torno de suposições POSIX, usando CHAR_BITS poderia dar a impressão de que o código poderia trabalhar com outros que 8. valores
supercat
2
@ Zack: Para que tipos não assinados de largura exata sejam úteis, sua semântica precisa ser definida independentemente do tamanho de int. Como é, dado uint32_t x,y,z;, o significado de (x-y) > zdepende do tamanho de int. Além disso, uma linguagem projetada para escrever código robusto deve permitir que os programadores façam a distinção entre um tipo em que os cálculos devem exceder o intervalo do tipo e devem ser encapsulados silenciosamente, versus aquele em que os cálculos que excedem o intervalo do tipo devem interceptar versus um em que os cálculos não é esperado que exceda o intervalo do tipo, mas ... #
1133
61

Então, o que há de errado em explicar código complicado com um comentário?

Não se trata de certo ou errado, mas de 'melhores práticas', conforme definido no artigo da Wikipedia :

Uma prática recomendada é um método ou técnica que sempre mostrou resultados superiores aos alcançados com outros meios e que é usado como referência.

Portanto, a melhor prática é tentar melhorar o código primeiro e usar o inglês, se isso não for possível.

Não é uma lei, mas é muito mais comum encontrar código comentado que exija refatoração do que código refatorado que exija comentários, a melhor prática reflete isso.

FMJaguar
fonte
42
+1 em "é muito mais comum encontrar código comentado que exija refatoração do que código refatorado que exija comentários"
Brandon
7
Ok, mas quantas vezes é que o comentário: //This code seriously needs a refactor
Erik Reppen
2
Obviamente, qualquer suposta melhor prática que não seja apoiada por um rigoroso estudo científico é apenas uma opinião.
Blrfl
54

Chegará o dia em que seu código bonito, perfeitamente criado, bem estruturado e legível não funcionará. Ou não funcionará bem o suficiente. Ou um caso especial surgirá onde não funcione e precise ser ajustado.

Nesse ponto, você precisará fazer algo que mude as coisas para que funcione corretamente. Especialmente nos casos em que há problemas de desempenho, mas também em cenários em que uma das bibliotecas, APIs, serviços da web, gemas ou sistemas operacionais com os quais você trabalha não se comporta conforme o esperado, você pode acabar fazendo sugestões que não são necessariamente deselegante, mas são contra-intuitivos ou não óbvios.

Se você não tiver comentários para explicar por que escolheu essa abordagem, há uma chance muito boa de que alguém no futuro (e que alguém possa até ser você) veja o código, veja como ele pode ser "corrigido" para algo mais legível e elegante e, inadvertidamente, desfaz sua correção, porque ela não parece uma correção.

Se todo mundo sempre escrevesse um código perfeito, seria óbvio que o código que parece imperfeito está contornando alguma intervenção complicada do mundo real, mas não é assim que as coisas funcionam. A maioria dos programadores costuma escrever códigos confusos ou um pouco confusos; portanto, quando o encontramos, é uma tendência natural resolvê-lo. Juro que meu eu passado é um idiota de verdade sempre que leio códigos antigos que escrevi.

Portanto, não penso nos comentários como um pedido de desculpas pelo código incorreto, mas talvez como uma explicação do motivo pelo qual você não fez a coisa óbvia. Ter // The standard approach doesn't work against the 64 bit version of the Frobosticate Librarypermitirá que futuros desenvolvedores, inclusive seu futuro, prestem atenção a essa parte do código e testem nessa biblioteca. Claro, você também pode colocar os comentários em seus commits de controle de origem, mas as pessoas só olharão para eles depois que algo der errado. Eles lerão os comentários do código à medida que mudam o código.

As pessoas que nos dizem que devemos sempre escrever código teoricamente perfeito nem sempre são pessoas com muita experiência em programação em ambientes do mundo real. Às vezes, você precisa escrever o código que executa com um certo nível; às vezes, você precisa interoperar com sistemas imperfeitos. Isso não significa que você não possa fazer isso de maneira elegante e bem escrita, mas soluções não óbvias precisam de explicação.

Quando escrevo códigos para projetos de hobby que sei que ninguém mais lerá, ainda comento partes que acho confusas - por exemplo, qualquer geometria 3D envolve matemática com a qual não estou totalmente em casa - porque sei quando volto em seis meses, esquecerei totalmente como fazer isso. Isso não é um pedido de desculpas por código incorreto, é um reconhecimento de uma limitação pessoal. Tudo o que eu faria, sem comentar, é criar mais trabalho para mim no futuro. Não quero que meu futuro precise reaprender algo desnecessariamente se puder evitá-lo agora. Que valor possível isso teria?

glenatron
fonte
5
@ Cristão é isso? A primeira linha faz referência a essa afirmação, certamente, mas além disso é um pouco mais ampla do que eu a entendo.
glenatron
9
"Eu juro que meu eu passado é um idiota de verdade sempre que leio códigos antigos que escrevi." Após quatro anos de minha carreira de desenvolvimento, acho que isso ocorre sempre que olho para algo com mais de seis meses.
Ken
6
Em muitos casos, as informações históricas mais informativas e úteis estão relacionadas a coisas que são consideradas, mas decididas contra. Existem muitos casos em que alguém escolhe a abordagem X para alguma coisa e alguma outra abordagem Y parece melhor; em alguns desses casos, Y "quase" funcionará melhor que X, mas acaba tendo alguns problemas insuperáveis. Se Y foi evitada por causa desses problemas, esse conhecimento pode ajudar a prevenir outros de desperdiçar seu tempo em tentativas frustradas de implementar abordagem Y.
supercat
4
No dia a dia, eu também uso muito os comentários sobre o trabalho em andamento - eles não estão lá a longo prazo, mas incluir uma nota do TODO ou uma seção curta para me lembrar o que eu faria a seguir pode ser útil lembrete de manhã.
glenatron
1
@ Lilienthal, não acho que o último parágrafo seja restrito a projetos pessoais - ele disse "... eu ainda comento partes que acho confusas".
Curinga
29

A necessidade de comentários é inversamente proporcional ao nível de abstração do código.

Por exemplo, a linguagem Assembly é, para fins mais práticos, ininteligível sem comentários. Aqui está um trecho de um pequeno programa que calcula e imprime termos da série Fibonacci :

main:   
; initializes the two numbers and the counter.  Note that this assumes
; that the counter and num1 and num2 areas are contiguous!
;
    mov ax,'00'                     ; initialize to all ASCII zeroes
    mov di,counter                  ; including the counter
    mov cx,digits+cntDigits/2       ; two bytes at a time
    cld                             ; initialize from low to high memory
    rep stosw                       ; write the data
    inc ax                          ; make sure ASCII zero is in al
    mov [num1 + digits - 1],al      ; last digit is one
    mov [num2 + digits - 1],al      ; 
    mov [counter + cntDigits - 1],al

    jmp .bottom         ; done with initialization, so begin

.top
    ; add num1 to num2
    mov di,num1+digits-1
    mov si,num2+digits-1
    mov cx,digits       ; 
    call    AddNumbers  ; num2 += num1
    mov bp,num2         ;
    call    PrintLine   ;
    dec dword [term]    ; decrement loop counter
    jz  .done           ;

    ; add num2 to num1
    mov di,num2+digits-1
    mov si,num1+digits-1
    mov cx,digits       ;
    call    AddNumbers  ; num1 += num2
.bottom
    mov bp,num1         ;
    call    PrintLine   ;
    dec dword [term]    ; decrement loop counter
    jnz .top            ;
.done
    call    CRLF        ; finish off with CRLF
    mov ax,4c00h        ; terminate
    int 21h             ;

Mesmo com comentários, pode ser bastante complicado grocá-lo.

Exemplo moderno: Regexes geralmente são construções de abstração muito baixas (letras minúsculas, número 0, 1, 2, novas linhas etc.). Eles provavelmente precisam de comentários na forma de amostras (Bob Martin, IIRC, reconhece isso). Aqui está uma regex que (eu acho) deve corresponder aos URLs HTTP (S) e FTP:

^(((ht|f)tp(s?))\://)?(www.|[a-zA-Z].)[a-zA-Z0-9\-\.]+\.(com|edu|gov|m
+il|net|org|biz|info|name|museum|us|ca|uk)(\:[0-9]+)*(/($|[a-zA-Z0-9\.
+\,\;\?\'\\\+&amp;%\$#\=~_\-]+))*$

À medida que os idiomas avançam na hierarquia de abstração, o programador pode usar abstrações evocativas (nome da variável, nomes de funções, nomes de classes, nomes de módulos, interfaces, retornos de chamada etc.) para fornecer documentação interna. Negligenciar tirar proveito disso e usar comentários para ocultar o papel é preguiçoso, um desserviço e desrespeito ao mantenedor.

Estou pensando em receitas numéricas em C traduzido principalmente na íntegra a receitas numéricas em C ++ , que eu inferir começou como receitas numéricas (em FORTAN), com todas as variáveis a, aa, b, c, cc, etc mantida através de cada versão. Os algoritmos podem estar corretos, mas não tiraram vantagem das abstrações fornecidas pelas linguagens. E eles me fodem. Amostra de um artigo do Dr. Dobbs - Fast Fourier Transform :

void four1(double* data, unsigned long nn)
{
    unsigned long n, mmax, m, j, istep, i;
    double wtemp, wr, wpr, wpi, wi, theta;
    double tempr, tempi;

    // reverse-binary reindexing
    n = nn<<1;
    j=1;
    for (i=1; i<n; i+=2) {
        if (j>i) {
            swap(data[j-1], data[i-1]);
            swap(data[j], data[i]);
        }
        m = nn;
        while (m>=2 && j>m) {
            j -= m;
            m >>= 1;
        }
        j += m;
    };

    // here begins the Danielson-Lanczos section
    mmax=2;
    while (n>mmax) {
        istep = mmax<<1;
        theta = -(2*M_PI/mmax);
        wtemp = sin(0.5*theta);
        wpr = -2.0*wtemp*wtemp;
        wpi = sin(theta);
        wr = 1.0;
        wi = 0.0;
        for (m=1; m < mmax; m += 2) {
            for (i=m; i <= n; i += istep) {
                j=i+mmax;
                tempr = wr*data[j-1] - wi*data[j];
                tempi = wr * data[j] + wi*data[j-1];

                data[j-1] = data[i-1] - tempr;
                data[j] = data[i] - tempi;
                data[i-1] += tempr;
                data[i] += tempi;
            }
            wtemp=wr;
            wr += wr*wpr - wi*wpi;
            wi += wi*wpr + wtemp*wpi;
        }
        mmax=istep;
    }
}

Como um caso especial de abstração, todo idioma possui idiomas / trechos de código canônico para determinadas tarefas comuns (excluindo uma lista vinculada dinâmica em C) e, independentemente de sua aparência, eles não devem ser documentados. Os programadores devem aprender esses idiomas, pois eles não fazem parte oficialmente do idioma.

Portanto, o que deve ser levado em consideração: o código não-idiomático criado a partir de blocos de baixo nível que não podem ser evitados precisa de comentários. E isso é necessário muito menos do que acontece.

Kristian H
fonte
1
Ninguém deve realmente estar escrevendo uma linha como esta em linguagem assembly: dec dword [term] ; decrement loop counter. Por outro lado, o que está faltando no seu exemplo de linguagem assembly é um comentário antes de cada "parágrafo de código", explicando o que o próximo bloco de código faz. Nesse caso, o comentário normalmente seria equivalente a uma única linha no pseudocódigo, como ;clear the screen, seguido pelas 7 linhas que realmente são necessárias para limpar a tela.
Scott Whitlock
1
Sim, há o que eu consideraria alguns comentários desnecessários na amostra de montagem, mas, para ser justo, é bastante representativo do estilo de montagem 'Bom'. Mesmo com um prólogo de parágrafo de uma ou duas linhas, o código seria realmente difícil de seguir. Eu entendi a amostra ASM melhor do que o exemplo FFT. Programei uma FFT em C ++ na pós-graduação, e ela não se parecia nada com isso, mas então estávamos usando o STL, iteradores, functors e algumas chamadas de método. Não é tão rápido quanto a função monolítica, mas é muito mais fácil de ler. Vou tentar adicioná-lo para contrastar com a amostra NRinC ++.
Kristian H
Você quis dizer ^(((ht|f)tps?)\:\/\/)?(www\.)*[a-zA-Z0-9\-\.]+\.(com|edu|gov|mil|net|org|biz|info|name|museum|us|ca|uk)(\:[0-9]+)*(\/($|[a-zA-Z0-9\.\,\;\?\'\\\+&%\$#\=~_\-]+))*$? Esteja ciente dos endereços numéricos.
Izabera 04/04
Mais ou menos o meu ponto: algumas coisas criadas a partir de abstrações de nível muito baixo não são fáceis de ler ou verificar. Comentários (e, para não ficar muito longe da trilha, TESTES) podem ser úteis e não prejudiciais. Ao mesmo tempo, não usar abstrações de nível superior disponíveis (: alpha:: num: onde disponível) dificulta a compreensão, mesmo com bons comentários, do que usar abstrações de nível superior.
Kristian H
3
+1: Resume "The need for comments is inversely proportional to the abstraction level of the code." praticamente tudo ali.
Gerrat 4/09/14
21

Não acredito que haja algo errado com os comentários no código. A ideia de que os comentários são de alguma forma ruins na minha opinião se deve a alguns programadores que levam as coisas longe demais. Há muita variação nesse setor, principalmente em relação a visões extremas. Em algum lugar ao longo do caminho, o código comentado tornou-se equivalente ao código incorreto e não sei por que.

Os comentários têm problemas - você precisa mantê-los atualizados à medida que atualiza o código a que se referem, o que acontece com pouca frequência. Um wiki ou algo assim é um recurso mais apropriado para documentação completa sobre seu código. Seu código deve ser legível sem exigir comentários. O controle de versão ou as notas de revisão devem ser onde você descreve as alterações de código feitas.

Nenhuma das opções acima invalida o uso de comentários, no entanto. Não vivemos em um mundo ideal; portanto, quando qualquer uma das opções acima falhar por qualquer motivo, prefiro ter alguns comentários para recuar.

Roy
fonte
18

Eu acho que você está lendo um pouco demais o que ele está dizendo. Existem duas partes distintas na sua reclamação:

O que há de errado em explicar (1) um algoritmo complexo ou (2) um pedaço longo e complicado de código com um comentário descritivo?

(1) é inevitável. Não acho que Martin discorde de você. Se você estiver escrevendo algo como a raiz quadrada inversa rápida , precisará de alguns comentários, mesmo que seja apenas "hacking de nível de bit de ponto flutuante maligno". Com exceção de algo simples, como uma DFS ou uma pesquisa binária, é improvável que a pessoa que está lendo seu código tenha experiência com esse algoritmo e, portanto, acho que deveria haver pelo menos uma menção nos comentários sobre o que é.

A maioria dos códigos não é (1), no entanto. Raramente você escreverá um software que não passa de implementações mutex feitas à mão, operações de álgebra linear obscuras com pouco suporte à biblioteca e novos algoritmos conhecidos apenas pelo grupo de pesquisa da sua empresa. A maioria dos códigos consiste em chamadas de biblioteca / estrutura / API, IO, clichê e testes de unidade.

Esse é o tipo de código que Martin está falando. E ele aborda sua pergunta com a citação de Kernighan e Plaugher no topo do capítulo:

Não comente código incorreto - reescreva-o.

Se você possui seções longas e complicadas em seu código, não conseguiu mantê-lo limpo . A melhor solução para esse problema não é escrever um comentário com um parágrafo no topo do arquivo para ajudar os futuros desenvolvedores a confundi-lo; a melhor solução é reescrevê-lo.

E é exatamente isso que Martin diz:

O uso adequado dos comentários é para compensar nossa falha em nos expressar em código ... Comentários são sempre falhas. Devemos tê-los, porque nem sempre podemos descobrir como nos expressar sem eles, mas o uso deles não é motivo de comemoração.

Este é o seu (2). Martin concorda que um código longo e complicado precisa de comentários - mas ele coloca a culpa por esse código nos ombros do programador que o escreveu, não uma idéia nebulosa de que "todos sabemos que isso não é suficiente". Ele argumenta que:

Um código claro e expressivo com poucos comentários é muito superior ao código complexo e desordenado, com muitos comentários. Em vez de gastar seu tempo escrevendo os comentários que explicam a bagunça que você fez, gaste-o limpando essa bagunça.

Patrick Collins
fonte
3
Se um desenvolvedor com quem eu estivesse trabalhando simplesmente escrevesse "hackers mal-intencionados em nível de bit de ponto flutuante" para explicar o algoritmo rápido de raiz quadrada - eles conversariam comigo. Enquanto eles incluíssem uma referência a algum lugar mais útil, eu ficaria feliz.
Michael Anderson
8
Eu discordo de uma maneira - um comentário explicando como algo ruim funciona é muito mais rápido. Dado um código que provavelmente não será tocado novamente (a maioria dos códigos, eu acho), um comentário é uma solução comercial melhor do que uma grande refatoração, que geralmente introduz bugs (como uma correção que mata um bug invocado ainda é um bug). Um mundo perfeito de código perfeitamente compreensível não está disponível para nós.
gbjbaanb
2
@trysis haha, sim, mas em um mundo em que os programadores são responsáveis ​​e não os empresários, eles nunca serão entregues, pois sempre colocam ouro em uma base de código constantemente refatorada em uma busca vã pela perfeição.
gbjbaanb
4
@PatrickCollins quase tudo o que eu leio na web é sobre fazê-lo da primeira vez. Quase ninguém quer escrever artigos sobre como arrumar bagunça! Físicos dizem "dada uma esfera perfeita ..." Comp.Scientists dizer "dado um desenvolvimento greenfield ..."
gbjbaanb
2
A melhor solução é reescrevê-lo com tempo infinito; mas considerando a base de código de outra pessoa, prazos corporativos típicos e realidade; Às vezes, a melhor coisa a fazer é comentar, adicionar um TODO: Refatorar e colocar o refator no próximo lançamento; e essa correção que precisava ser feita ontem foi feita agora. A coisa sobre toda essa conversa idealista sobre apenas refatoração é que ela não explica como as coisas realmente funcionam no local de trabalho; Às vezes, existem prioridades mais altas e prazos em breve que impedirão a fixação de códigos legados de baixa qualidade. É assim mesmo.
hsanders
8

O que há de errado em explicar um algoritmo complexo ou um pedaço longo e complicado de código com um comentário descritivo?

Nada como tal. Documentar seu trabalho é uma boa prática.

Dito isto, você tem uma falsa dicotomia aqui: escrever código limpo vs. escrever código documentado - os dois não estão em oposição.

O que você deve focar é simplificar e abstrair o código complexo em um código mais simples, em vez de pensar que "o código complexo é bom desde que seja comentado".

Idealmente, seu código deve ser simples e documentado.

Dessa forma, em vez de outros desenvolvedores (inclusive você) terem que ler toda a linha algoritmo por linha para descobrir o que ele faz, eles podem simplesmente ler o comentário descritivo amigável que você escreveu em inglês simples.

Verdadeiro. É por isso que todos os seus algoritmos de API pública devem ser explicados na documentação.

Então, depois de escrever um pedaço complexo de código, escrito em uma linguagem de programação parcialmente legível por humanos, por que não adicionar um comentário descritivo e conciso que explique a operação do código em inglês amigável e compreensível?

Idealmente, depois de escrever um pedaço de código complexo, você deve (não uma lista exaustiva):

  • considere um rascunho (ou seja, planeje reescrevê-lo)
  • formalizar os pontos de entrada / interfaces / funções / etc do algoritmo (analisar e otimizar a interface, formalizar abstrações, pré-condições de documentos, pós-condições e efeitos colaterais e documentar casos de erro).
  • escrever testes
  • limpeza e refatoração

Nenhuma dessas etapas é trivial de executar (ou seja, cada uma pode levar algumas horas) e as recompensas por executá-las não são imediatas. Como tal, essas etapas são (quase) sempre comprometidas (por desenvolvedores cortando cantos, gerentes cortando cantos, prazos, restrições de mercado / outras condições do mundo real, falta de experiência etc.).

[...] alguns algoritmos são complexos. E, portanto, é difícil de entender ao lê-los linha por linha.

Você nunca deve confiar na leitura da implementação para descobrir o que uma API faz. Ao fazer isso, você está implementando o código do cliente com base na implementação (em vez da interface) e isso significa que o acoplamento do seu módulo está pronto, você está potencialmente introduzindo dependências não documentadas com cada nova linha de código que escreve e está já adicionando dívida técnica.

É realmente ruim explicar um algoritmo complexo com algumas linhas de comentários sobre sua operação geral?

Não - isso é bom. Adicionar algumas linhas de comentários não é suficiente.

O que há de errado em explicar código complicado com um comentário?

O fato de você não ter código complicado, se isso puder ser evitado.

Para evitar código complicado, formalize suas interfaces, gaste ~ 8 vezes mais em design de API do que em implementação (Stepanov sugeriu gastar pelo menos 10x na interface, em comparação com a implementação) e desenvolva um projeto com o conhecimento de que você está criando um projeto, não apenas escrevendo algum algoritmo.

Um projeto envolve documentação da API, documentação funcional, medições de código / qualidade, gerenciamento de projetos e assim por diante. Nenhum desses processos é uma etapa rápida e única a ser executada (todos eles levam tempo, exigem reflexão e planejamento, e todos exigem que você volte periodicamente a eles e os revise / complete com detalhes).

utnapistim
fonte
3
"Você nunca deve confiar na leitura da implementação para descobrir o que uma API faz." Às vezes, isso é infligido a você por um montante que você está comprometido em usar. Eu tive um projeto particularmente insatisfatório repleto de comentários do formulário "o seguinte código feio de Heath Robinson existe porque simpleAPI () não funciona corretamente nesse hardware, apesar do que o fornecedor alega".
Pjc50
6

em vez de outros desenvolvedores (inclusive você) terem que ler o algoritmo inteiro linha por linha para descobrir o que ele faz, eles podem ler o comentário descritivo amigável que você escreveu em inglês simples.

Eu consideraria isso um pequeno abuso de "comentários". Se o programador quiser ler algo em vez de todo o algoritmo, é para isso que serve a documentação da função. OK, a documentação da função pode realmente aparecer nos comentários na fonte (talvez para extração por ferramentas de documentos), mas embora sintaticamente seja um comentário no que diz respeito ao seu compilador, você deve considerá-los como itens separados com propósitos separados. Não acho que "os comentários devam ser escassos" significa necessariamente "a documentação deve ser escassa" ou mesmo "avisos de direitos autorais devem ser escassos"!

Os comentários na função são para alguém ler , bem como o código. Portanto, se você tem algumas linhas difíceis de entender no código e não pode facilitar a compreensão, um comentário é útil para o leitor usar como espaço reservado para essas linhas. Isso pode ser muito útil enquanto o leitor está apenas tentando obter a essência geral, mas existem alguns problemas:

  • Os comentários não são necessariamente verdadeiros, enquanto o código faz o que faz. Portanto, o leitor está acreditando na sua palavra e isso não é o ideal.
  • O leitor ainda não entende o código; portanto, até que retornem posteriormente, ainda não estão qualificados para modificá-lo ou reutilizá-lo. Nesse caso, o que eles estão fazendo lendo isso?

Existem exceções, mas a maioria dos leitores precisará entender o próprio código. Os comentários devem ser escritos para ajudá-lo, não para substituí-lo, e é por isso que você geralmente recomenda que os comentários digam "por que você está fazendo isso". Um leitor que conhece a motivação para as próximas linhas de código tem mais chances de ver o que faz e como.

Steve Jessop
fonte
5
Um local útil para comentários: no código científico, geralmente é possível ter cálculos bastante complexos, envolvendo muitas variáveis. Para a sanidade do programador, faz sentido manter os nomes das variáveis ​​muito curtos, para que você possa ver a matemática e não os nomes. Mas isso torna muito difícil de entender para o leitor. Portanto, uma breve descrição do que está acontecendo (ou melhor, uma referência à equação em um artigo de jornal ou similar) pode ser realmente útil.
precisa saber é o seguinte
1
@ naught101: sim, especialmente porque o artigo ao qual você está se referindo também provavelmente usou nomes de variáveis ​​de uma letra. Geralmente, é mais fácil ver que o código realmente segue o artigo se você usar os mesmos nomes, mas isso está em conflito com o objetivo do código ser auto-explicativo (é explicado pelo artigo ). Nesse caso, um comentário em que cada nome é definido, dizendo o que realmente significa, substitui nomes significativos.
Steve Jessop
1
Quando estou procurando por algo específico no código (onde esse caso específico é tratado?), Não quero ler e entender parágrafos de código apenas para descobrir que, afinal, não é o lugar. Preciso de comentários que resumam em uma única linha o que o próximo parágrafo está fazendo. Dessa forma, localizarei rapidamente as partes do código relacionadas ao meu problema e pularemos detalhes desinteressantes.
Florian F
1
@FlorianF: a resposta tradicional é que os nomes de variáveis ​​e funções devem indicar aproximadamente o que é o código e, portanto, permitir que você examine rapidamente coisas que certamente não são sobre o que você está procurando. Concordo com você que isso nem sempre é bem-sucedido, mas não concordo tão fortemente que acho que todo o código precisa ser comentado para ajudar na pesquisa ou na leitura. Mas você está certo, é um caso em que alguém está lendo seu código (mais ou menos) e legitimamente não precisa entendê-lo.
Steve Jessop
2
@ Snowman As pessoas poderiam fazer isso com nomes de variáveis. Eu vi código em que a variável listOfApples continha uma lista de bananas. Alguém copiou o código que processava a lista de maçãs e o adaptou para o Bananas sem se preocupar em alterar os nomes das variáveis.
Florian F
5

Muitas vezes temos que fazer coisas complicadas. Certamente é certo documentá-los para uma compreensão futura. Às vezes, o local certo para esta documentação está no código, onde a documentação pode ser mantida atualizada com o código. Mas definitivamente vale a pena considerar a documentação separada. Também pode ser mais fácil apresentar a outras pessoas, incluir diagramas, figuras coloridas etc. Então o comentário é apenas:

// This code implements the algorithm described in requirements document 239.

ou mesmo apenas

void doPRD239Algorithm() { ...

Certamente as pessoas estão felizes com as funções nomeadas MatchStringKnuthMorrisPrattou encryptAESou partitionBSP. Nomes mais obscuros merecem explicação em um comentário. Você também pode adicionar dados bibliográficos e um link para um artigo do qual você implementou um algoritmo.

Se um algoritmo é complexo, novo e não é óbvio, definitivamente vale a pena um documento, mesmo que seja apenas para circulação interna da empresa. Verifique o documento no controle de origem, se estiver preocupado com a perda.

Há outra categoria de código que não é tão algorítmica quanto burocrática. Você precisa configurar parâmetros para outro sistema ou interoperar com os erros de outra pessoa:

/* Configure the beam controller and turn on the laser.
The sequence is timing-critical and this code must run with interrupts disabled.
Note that the constant 0xef45ab87 differs from the vendor documentation; the vendor
is wrong in this case.
Some of these operations write the same value multiple times. Do not attempt
to optimise this code by removing seemingly redundant operations.
*/
pjc50
fonte
2
Eu argumentaria contra nomear funções / métodos após o algoritmo interno deles / delas, na maioria das vezes o método usado deveria ser uma preocupação interna, por todos os meios documentar a parte superior da sua função com o método usado, mas não chame isso doPRD239Algorithmque me diz nada sobre a função sem ter que procurar o algoritmo, a razão MatchStringKnuthMorrisPratte o encryptAEStrabalho é que eles começam com uma descrição do que fazem e, em seguida, seguem com uma descrição da metodologia.
scragar
5

Eu esqueço onde eu lê-lo, mas não é uma linha nítida e clara entre o que deve aparecer no seu código e que deve aparecer como um comentário.

Eu acredito que você deve comentar sua intenção, não seu algoritmo . Ou seja, comente o que você pretendia fazer, não o que você faz .

Por exemplo:

// The getter.
public <V> V get(final K key, Class<V> type) {
  // Has it run yet?
  Future<Object> f = multitons.get(key);
  if (f == null) {
    // No! Make the task that runs it.
    FutureTask<Object> ft = new FutureTask<Object>(
            new Callable() {

              public Object call() throws Exception {
                // Only do the create when called to do so.
                return key.create();
              }

            });
    // Only put if not there.
    f = multitons.putIfAbsent(key, ft);
    if (f == null) {
      // We replaced null so we successfully put. We were first!
      f = ft;
      // Initiate the task.
      ft.run();
    }
  }
  try {
    /**
     * If code gets here and hangs due to f.status = 0 (FutureTask.NEW)
     * then you are trying to get from your Multiton in your creator.
     *
     * Cannot check for that without unnecessarily complex code.
     *
     * Perhaps could use get with timeout.
     */
    // Cast here to force the right type.
    return (V) f.get();
  } catch (Exception ex) {
    // Hide exceptions without discarding them.
    throw Throwables.asRuntimeException(ex);
  }
}

Aqui não há nenhuma tentativa de indicar o que cada etapa executa, tudo o que afirma é o que deve fazer.

PS: Encontrei a fonte à qual me referia - Horror em codificação: o código diz como, os comentários dizem o porquê

OldCurmudgeon
fonte
8
O primeiro comentário: ele já foi executado? O que já correu? O mesmo para os outros comentários. Para alguém que não sabe o que o código faz, isso é inútil.
precisa saber é o seguinte
1
@ gnasher729 - Tirado do contexto, quase todo comentário será inútil - esse código é uma demonstração da adição de comentários que indicam intenção em vez de tentar descrever . Lamento que isso não faça nada para você.
OldCurmudgeon
2
Um mantenedor desse código não terá um contexto. Não é particularmente difícil descobrir o que o código faz, mas os comentários não estão ajudando. Se você escrever comentários, não se apresse e concentre-se em escrevê-los.
precisa saber é o seguinte
BTW - O comentário Já executou está se referindo ae Futureindica que um get()seguido por uma verificação para nulldetecta se a Futurejá foi executada - documentando corretamente a intenção e não o processo .
OldCurmudgeon
1
@ OldCurmudgeon: Sua resposta está próxima o que eu estava pensando, para que eu apenas adicione este comentário como um exemplo de seu argumento. Embora não seja necessário um comentário para explicar o código limpo, é bom explicar por que a codificação foi feita UMA MANEIRA OU OUTRA. Na minha experiência limitada, os comentários geralmente são úteis para explicar as idiossincrasias do conjunto de dados em que o código está trabalhando ou as regras de negócios que o código deve aplicar. O código de comentário adicionado para corrigir um erro é um bom exemplo, se esse erro ocorreu porque uma suposição sobre os dados estava errada.
Randall Stewart
4

Mas todos sabemos que isso não é suficiente.

Realmente? Desde quando?

Código bem projetado com bons nomes é mais que suficiente na grande maioria dos casos. Os argumentos contra o uso de comentários são bem conhecidos e documentados (como você se refere).

Mas estas são diretrizes (como qualquer outra coisa). No raro caso (na minha experiência, cerca de uma vez a cada 2 anos) em que as coisas seriam piores quando refatoradas para funções legíveis menores (devido a necessidades de desempenho ou coesão), vá em frente - faça um longo comentário explicando o que realmente é a coisa fazendo (e por que você está violando as melhores práticas).

Telastyn
fonte
7
Eu sei que não é suficiente.
Florian F
2
Desde quando? Aparentemente, você já sabe a resposta para isso. "Código bem projetado com bons nomes é mais que suficiente na grande maioria dos casos". Portanto, provavelmente não é suficiente em uma minoria de casos, exatamente o que o solicitante está perguntando.
Ellesedil 2/09/2014
3
Estou sempre tentando decifrar o código de outras pessoas, que eu gostaria de ter acrescentado alguns comentários mais de uma vez a cada dois anos.
Ogre Psalm33
@ OgrePsalm33 - Eles têm métodos pequenos e usam bons nomes? Código incorreto é inválido, independentemente de comentários.
Telastyn
2
@Telastyn Infelizmente, ao trabalhar em uma grande base de código, métodos "pequenos" e nomes "bons" são subjetivos para cada desenvolvedor (o mesmo é um bom comentário). Um desenvolvedor que escreve o código do algoritmo de processamento gráfico de Flarbigan por 7 anos, pode escrever algo perfeitamente claro para ele e desenvolvedores similares, mas seria enigmático para o novo cara que passou os últimos 4 anos desenvolvendo o código de infraestrutura de grade do Peru. Então, duas semanas depois, o especialista em Flarbigan sai.
Ogre Psalm33
2

O principal objetivo do código é comandar um computador para fazer algo; portanto, um bom comentário nunca substitui um bom código, porque os comentários não podem ser executados.

Dito isto, os comentários na fonte são uma forma de documentação para outros programadores (inclusive você). Se os comentários são sobre questões mais abstratas do que o que o código está fazendo a cada etapa, você está se saindo melhor que a média. Esse nível de abstração varia de acordo com a ferramenta que você está usando. Os comentários que acompanham as rotinas da linguagem assembly geralmente têm um nível mais baixo de "abstração" do que, por exemplo, este APL A←0⋄A⊣{2⊤⍵:1+3×⍵⋄⍵÷2}⍣{⍺=A+←1}⎕. Eu acho que provavelmente mereceria um comentário sobre o problema que ele pretende resolver, hmmm?

Scott Leadley
fonte
2

Se o código é trivial, ele não precisa de um comentário explicativo. Se o código não for trivial, o comentário explicativo provavelmente também não será trivial.

Agora, o problema com a linguagem natural não trivial é que muitos de nós não somos muito bons em ler ou escrever. Tenho certeza de que suas habilidades de comunicação escrita são excelentes, mas, mesmo assim, alguém com uma compreensão menor da linguagem escrita pode entender mal suas palavras.

Se você se esforçar muito para escrever uma linguagem natural que não possa ser mal interpretada, você acaba com um documento legal (e como todos sabemos, essas são mais detalhadas e difíceis de entender que o código).

Código deve ser a descrição mais concisa de sua lógica e não deve haver muito debate sobre o significado de seu código, porque seu compilador e plataforma têm a palavra final.

Pessoalmente, eu não diria que você nunca deve escrever um comentário. Somente você deve considerar por que seu código precisa de um comentário e como você pode corrigi-lo. Este parece ser um tema comum nas respostas aqui.

Martin
fonte
Exatamente o que eu estava pensando quando discordei da afirmação "Um ser humano pode entender um pedaço de inglês muito mais rapidamente que ele / ela pode entender um pedaço de código com o mesmo significado (desde que a operação não seja trivial)" sempre menos ambíguo e mais conciso.
stephenbayer
0

Um ponto ainda não mencionado é que, às vezes, comentar com precisão o que um pedaço de código faz pode ser útil nos casos em que uma linguagem usa uma sintaxe específica para vários propósitos. Por exemplo, supondo que todas as variáveis ​​sejam do tipo float, considere:

f1 = (float)(f2+f3); // Force result to be rounded to single precision
f4 = f1-f2;

O efeito de converter explicitamente um floatpara floaté forçar o resultado a ser arredondado para uma precisão única; o comentário poderia, portanto, ser visto como simplesmente dizendo o que o código faz. Por outro lado, compare esse código com:

thing.someFloatProperty = (float)(f2*0.1); // Divide by ten

Aqui, o objetivo do elenco é impedir que o compilador grite da maneira mais eficiente de calcular com precisão (f2 / 10) [é mais preciso do que multiplicar por 0,1f, e na maioria das máquinas é mais rápido que dividir por 10,0f].

Sem o comentário, alguém que estivesse revisando o código anterior poderia pensar que o elenco foi adicionado com uma crença equivocada de que seria necessário impedir o compilador de gritar e que não era necessário. De fato, o elenco serve para o propósito de fazer exatamente o que a especificação da linguagem diz que faz: forçar o resultado do cálculo a ser arredondado para precisão única, mesmo em máquinas onde o arredondamento seria mais caro do que manter o resultado em maior precisão. Dado que um elenco floatpode ter vários significados e propósitos diferentes, ter um comentário especificando qual significado é pretendido em um cenário específico pode ajudar a deixar claro que o significado real está alinhado com a intenção.

supercat
fonte
Não tenho certeza de que J. Random Programmer, olhando para o segundo exemplo, perceberá que a constante é escrita 0,1 por uma boa razão, e não porque o programador original esqueceu de digitar 'f'.
David K
Especialmente durante a depuração, você nunca assume que algo foi feito por um bom motivo.
gnasher729
@ DavidD: O objetivo do meu segundo exemplo de código era contrastá-lo com o primeiro pedaço de código. No segundo trecho de código, a intenção do programador é provavelmente ter someFloatPropertya representação mais precisa f2/10possível; o objetivo principal do segundo elenco é, portanto, simplesmente compilar o código . No primeiro exemplo, no entanto, o elenco claramente não é necessário para seu objetivo normal (alterando um tipo de tempo de compilação para outro), pois os operandos já o são float. O comentário serve para esclarecer que o elenco é necessário para uma finalidade secundária (arredondamento).
Supercat
Eu concordo com a noção de que você não precisa fazer nenhum comentário sobre o (float)elenco no segundo exemplo. A questão é sobre a constante literal 0.1. Você explicou (no próximo parágrafo do texto) por que escreveríamos 0.1: "é mais preciso do que multiplicar por 0,1f". Estou sugerindo que essas são as palavras que devem constar no comentário.
David K
@ DavidDK: Eu certamente incluiria o comentário se soubesse que 0.1f seria inaceitavelmente impreciso e usaria 0.1f se soubesse que a perda de precisão seria aceitável e que 0.1f seria de fato materialmente mais rápido que 0.1 . Se não sei se uma dessas coisas é verdadeira, o meu hábito de codificação preferido seria o de usar doublepara constantes ou cálculos intermediários cujo valor pode não ser representável, pois float[em idiomas que exigem lançamentos explícitos irritantes de duplo para flutuar, preguiça pode ser o uso de floatconstantes, não para velocidade, mas para minimizar o aborrecimento].
Supercat
-1

Comentários que explicam o que o código faz são uma forma de duplicação. Se você alterar o código e esquecer de atualizar os comentários, isso pode causar confusão. Não estou dizendo para não usá-los, apenas use-os criteriosamente. Eu assino a máxima do tio Bob: "Apenas comente o que o código não pode dizer".

murungu
fonte