3 pontos por GN⁺ 2024-09-12 | 1 comentários | Compartilhar no WhatsApp
  • Comentários podem usar linguagem humana, mais expressiva que identificadores, por isso são adequados para registrar alternativas que não entraram no código e opções abandonadas
  • “comment the why, not the what” é uma abordagem que busca colocar o máximo de informação possível nos identificadores, mas recentemente tem surgido uma tendência de mover até os motivos para nomes longos de funções ou de testes
  • No build do epub de Logic for Programmers, foi usada uma implementação lenta que substitui sequencialmente 16 símbolos matemáticos em cada string, mas atualmente existem apenas 25 strings matemáticas, então isso é rápido o suficiente
  • Esse tipo de comentário indica, no futuro, onde mexer quando as strings matemáticas crescerem para centenas e virarem um gargalo no build, além de registrar que o código lento foi um trade-off consciente
  • Como nomes de função e testes se ligam ao que o código realmente faz, fica difícil autodocumentar informação negativa — alternativas não escolhidas e coisas que o código não faz

Informações que comentários guardam com mais facilidade que o código

  • Código é uma linguagem de máquina estruturada, enquanto comentários são escritos em linguagem humana mais expressiva
  • “comment the why, not the what” pode ser interpretado como a ideia de colocar o máximo possível de informação nos identificadores
  • Identificadores se parecem com uma forma limitada de linguagem humana embutida no código; eles não conseguem carregar todo “what”, mas em muitos casos conseguem carregar bastante
  • Recentemente, vem crescendo a visão de que até o “why” pode ir para nomes longos de funções ou nomes de casos de teste, em vez de comentários
  • Codebases autodocumentadas em geral ampliam a documentação por meio de mais identificadores
    • Como exceção, há casos em que se diz que trocar comentários por logging torna o código mais autodocumentado

O que comentários “Why Not” tratam

  • A informação mais difícil de expressar em código é a informação negativa
  • Informação negativa chama atenção para o que não existe no sistema e para por que certas alternativas não foram escolhidas
  • “why not” não explica o que o código faz; ele registra quais escolhas o código não faz e por quê

Caso de substituição de símbolos matemáticos no epub

  • No build do epub de Logic for Programmers, por motivos técnicos, notações matemáticas como \forall não eram convertidas em símbolos como
  • Para resolver isso, foi escrito um script que troca diretamente os tokens dentro das strings matemáticas por seus símbolos Unicode correspondentes
  • A implementação mais fácil é chamar string = string.replace(old, new) para cada um dos 16 símbolos matemáticos necessários
  • Esse método é ineficiente porque percorre cada string 16 vezes, mas uma abordagem que faz as 16 substituições em uma única passada é mais complexa
  • A ideia central do comentário deixado foi a seguinte
    • cada string é percorrida 16 vezes
    • o livro atualmente tem apenas 25 strings matemáticas e a maioria tem menos de 5 caracteres
    • por isso, ainda é rápido o suficiente
  • Esse comentário explica não só “por que usar código lento”, mas também “por que não usar código rápido”

Uma placa para o leitor do futuro

  • Mesmo que o código lento não cause problema agora, ele pode virar problema depois
  • Se versões futuras de Logic for Programmers passarem a ter centenas de strings matemáticas, essa etapa do build pode virar o gargalo do processo inteiro
  • Deixar essa placa agora permite saber imediatamente, no futuro, qual parte precisa ser corrigida
  • Mesmo que o código continue funcionando sem problemas, o comentário preserva o fato de que o autor conhecia o trade-off
  • Quando alguém reabrir epub_math_fixer.py dois anos depois, haverá menos necessidade de investigar se o código lento veio de inexperiência, falta de tempo ou erro
  • Um comentário negativo registra que a implementação lenta era conhecida, que alternativas foram avaliadas e que se decidiu não otimizar

Por que é difícil substituir isso por nomes de função e testes

  • Um nome como RunFewerTimesSlowerAndSimplerAlgorithmAfterConsideringTradeOffs é longo demais e ainda não explica o trade-off de forma suficiente
  • Se o código for otimizado depois, talvez seja preciso mudar esse nome em vários lugares
  • O problema maior é que esse tipo de nome não informa o que a função realmente faz, enfraquecendo a autodocumentação em vez de fortalecê-la
  • Identificadores como nomes de função e variável só conseguem carregar uma única cláusula de informação
  • É difícil colocar ao mesmo tempo, em um único identificador, “o que a função faz” e “qual trade-off ela assume”

Informação negativa que também é difícil deixar em testes

  • Seria possível criar um teste que faz grep nos blocos matemáticos do livro e falha se a contagem passar de 80
  • Mas esse teste não testa diretamente o próprio EpubMathFixer
  • Não há, dentro da função, um ponto ao qual esse teste possa se prender
  • A autodocumentação acompanha o código escrito e explica o que o código faz
  • Como a informação negativa trata do que o código não faz, ela não combina, em sua base, com a lógica de autodocumentação

Uma pergunta mais ampla

  • Comentários “why not” podem ser vistos como um caso de contrafactualidade
  • Fica a questão de até que ponto elementos abstratos da comunicação humana podem, em geral, ser autodocumentados
  • Ainda permanece em aberto se informações como metáfora, incerteza e argumentos éticos também podem ser autodocumentadas

1 comentários

 
GN⁺ 2024-09-12
Opiniões no Hacker News
  • Lembro de uma piada que acho que vi no Twitter alguns anos atrás: era algo como “engenheiros júnior escrevem comentários explicando o que o código faz, engenheiros plenos escrevem comentários explicando por que o código faz aquilo, e engenheiros sênior escrevem comentários explicando por que o código não foi escrito de outro jeito”

    • É exatamente isso. Já precisei colocar um comentário 5 vezes maior que o próprio código, para impedir que alguém que não conhecesse o código, o domínio ou os cuidados ao lidar com um grande escopo dinâmico fizesse uma refatoração bem-intencionada
      Por outro lado, quando o significado podia ficar claro só com nomes de funções e variáveis, já escrevi funções bem grandes sem comentário nenhum
    • Faço os três. Comentários de resumo são extremamente úteis e já foram validados por estudos empíricos, mas muita gente parece ter mergulhado tão fundo no pensamento estilo Clean Code que é difícil resgatar
    • Programadores júnior normalmente não documentam nada ou documentam tudo. Com mais experiência, aprendem que basta documentar o que é incomum; com ainda mais experiência, as coisas incomuns ficam cada vez mais raras, então os comentários também diminuem
      Por isso, menos comentários acaba vencendo, mas, ao ver uma base de código sem comentários, só olhando de perto dá para saber se aquilo é uma obra-prima belamente lapidada ou um código instável feito por iniciantes
    • Também há comentários explicando por que é preciso continuar reproduzindo um comportamento que parece obviamente idiota. É porque alguma outra coisa depende daquele comportamento idiota
    • Um engenheiro de nível C deixaria um comentário como: “Foram desperdiçadas X horas refatorando este código. Se decidiu seguir os passos dos seus antecessores, incremente este contador”
      Mesmo que pareça uma gambiarra completa, às vezes isso é de fato o melhor que dá para conseguir
  • Deixo em comentário tudo que acho que será útil para mim quando eu voltar a olhar o código um ano depois. Normalmente é o porquê e o por que não, e, quando o código é complexo, também escrevo um breve “o quê” para enxergar melhor o fluxo
    O que não é útil são comentários obrigatórios. APIs públicas devem ser bem documentadas, mas algumas organizações exigem comentário em todas as funções, até nas privadas, e às vezes o propósito é tão óbvio que o comentário só repete o nome da função. Isso não só desperdiça tempo como também nos deixa insensíveis a comentários e ensina o hábito de ignorá-los
    Também não gosto de comentários inúteis adicionados por ferramentas. Colocar //for ou //try em todo loop ou bloco do tipo é especialmente ruim

    • Estranhamente, muitos esquemas de cores de realce de sintaxe deixam os comentários apagados, com baixo contraste. Provavelmente porque comentários obrigatórios ou gerados costumam ter pouca informação
      Seria melhor eliminar comentários obrigatórios e gerados e, em temas escuros, transformar comentários em cores neon claras para que se destaquem. Se há um comentário, isso deveria significar que ele é importante
    • Antes eu tinha a posição de que “todo comentário é code smell”, e ainda penso isso em grande parte, mas trabalhar em uma base de código muito grande, desenvolvida e mantida ativamente por centenas de pessoas, suavizou um pouco minha visão
      Para manter sistemas grandes e antigos em um ambiente de negócios com prazos apertados, às vezes é preciso fazer coisas estranhas, e nesses casos é necessário explicar por quê
      Um grande motivo para ser contra comentários é que eles também passam a fazer parte do código e precisam de manutenção. Mas a maioria das pessoas só lê comentários quando fica travada, e os editores também os deixam em cinza apagado, tornando-os psicologicamente invisíveis. Por isso, comentários envelhecem com facilidade
      Fico curioso se o contexto escrito para o “eu do futuro” também é útil em uma base de código compartilhada tocada por muitos desenvolvedores, ou se é uma abordagem que funciona melhor quando apenas poucas pessoas mexem no código
    • Concordo totalmente. Comentários demais dificultam ver o que existe dentro de uma classe ou função. Se uma classe ou função que caberia em uma tela passa a ultrapassar uma tela por causa dos comentários, há um custo de legibilidade
    • Ontem, em um projeto pessoal, vi uma linha com um comentário “isso é realmente útil?” e parecia que dava para removê-la facilmente. Tentei usar uma classe nova e limpa, mas aquele jeito antigo específico era de fato necessário
      Então acrescentei “=> yes!” ao comentário existente e fiquei grato ao meu eu do passado por ter documentado aquela dúvida. No trabalho, especialmente ao corrigir bugs, costumo deixar uma ou duas linhas de comentário sobre mudanças não óbvias, junto com o número do ticket
  • O formato de comentário de que mais gosto entre os que já deixei no código é este template: “DEAR MAINTAINER: este código está assim por [motivo]. Se, ao tentar ‘consertá-lo’, você perceber que foi um erro terrível, incremente o contador total_hours_wasted_here = n como aviso para a próxima pessoa”
    Não sou o autor original, mas já usei com gratidão uma ou duas vezes, e era divertido ver commits de uma linha só incrementando o contador

    • Eu gostaria de ter tido isso alguns anos atrás. Certa vez criei um código de geração de SQL que, para satisfazer os requisitos, precisava usar recursão de forma bastante livre; havia muita recursão mútua, então o código ficou meio bagunçado, mas era um mal necessário
      Um engenheiro mais sênior assumiu a base de código e “consertou” tudo transformando em laços; tentou me dar uma bronca por e-mail sobre por que recursão era ruim, mas o código dele não atendia a todos os requisitos reais e, no fim, ele basicamente recriou as coisas que eu tinha feito com recursão
      Mais tarde ele até se desculpou por algumas das falas, mas talvez toda a situação pudesse ter sido evitada se eu tivesse colocado um comentário desses no topo
  • Concordo que o título é ambíguo, e foi por isso que acabei lendo o texto. Pessoalmente, prefiro usar poucos comentários no geral, mas os comentários explicativos mencionados no texto claramente têm valor. É um bom lembrete para explicar por que algo foi feito daquele jeito e por que não de outro
    Isso se aplica especialmente ao próprio código que você terá de manter daqui a 5, 10 ou 15 anos. Recentemente, ao revisar código novo de um colega, pensei “por que ele fez assim?”, mas 10 linhas acima havia exatamente o mesmo motivo que eu tinha deixado 8 anos antes. O colega estava seguindo uma regra central de manutenção: fazer parecer com o código existente

    • Ao manter uma base de código antiga, fazer parecer com o código existente é subestimado demais. Pelo bem da saúde mental de quem vier depois, por favor, façam parecer com o código existente
  • É apenas um caso especial de um princípio mais amplo: comentar aquilo que é surpreendente ao ler o código
    Quem, ao escrever código, fica se perguntando na cabeça “vou conseguir entender este código depois?” e sempre responde instintivamente “sim” é arrogante e frequentemente está errado. Se a resposta for “não tenho certeza”, a próxima pergunta naturalmente é “por quê?”, e essa resposta é justamente o que deve ir no comentário
    Às vezes a resposta é “porque quem lê pode se perguntar por que não foi escrito de outro jeito”, que é o caso especial tratado neste texto. Mas, às vezes, é “porque não está claro como funciona ou por que está correto”, e aí é preciso outro tipo de comentário

    • Se você primeiro tentou escrever o código de um jeito e não funcionou, exigindo uma segunda abordagem, isso é um forte sinal de que aquele ponto precisa de um comentário. Se você se surpreendeu durante a escrita, é bem provável que daqui a um ano já tenha esquecido essa surpresa e volte a se surpreender ao ler o código
    • Um princípio parecido é usar como critério “se isto não funcionar como esperado, onde eu devo olhar?”. Se a resposta não estiver na documentação, ou estiver a mais de 10 linhas de distância no caminho wiki → pacote/módulo → arquivo → classe → função/método, adiciono um comentário inline ou atualizo a documentação
      Isso costuma acontecer principalmente ao cortar strings ou ao explorar, como etapa intermediária, uma estrutura de dados incomum
  • Só com identificadores dá para ir muito longe, mas não até o fim. Pessoalmente, gosto de exigir comentários de documentação, como jsdoc/xmldoc, para métodos públicos, variáveis, campos e parâmetros
    Dar bons nomes aos métodos também é importante, mas tentar escrever brevemente o que eles fazem deixa tudo mais claro e, em especial, revela defeitos óbvios. Muitas vezes, no momento em que se escreve a primeira frase, surge um nome melhor; e, quando a explicação começa a incluir “e”, isso é sinal de que o método está fazendo coisas demais e pode ser dividido de forma mais lógica
    É fácil achar que propriedades são tão claras que não precisam de documentação, mas algo como /** The API key */ string ApiKey; deixa coisa demais de fora. Não dá para saber de onde vem essa chave, se é só para uso interno ou se é trocada com sistemas externos, se é obrigatória, se pode ser null ou vazia, se há tamanho máximo, o que acontece com valores inválidos, ou se há mais código ou documentação para ler
    Para o autor original, são informações que levam 1 ou 2 minutos para escrever, mas uma pessoa nova pode levar horas para descobri-las ao modificar ou usar aquilo, ou ao ser chamada anos depois para corrigir um bug

  • Em revisões de código, quando consigo prever o que um revisor excessivamente minucioso vai dizer, costumo escrever comentários como “não fiz X por causa de Y”. O objetivo é reduzir idas e vindas incômodas na discussão

    • Pela minha experiência, o volume de idas e vindas continua igual, mas ao menos dá para escrever algumas vezes “como está no comentário”
    • Eu acrescento esse tipo de informação preventivamente no PR, mas não tenho tanta certeza de que valha muito a pena deixá-la no código
  • Comentários do tipo “percorremos cada string 16 vezes, mas o livro tem só 25 strings de fórmulas até agora, e a maioria tem menos de 5 caracteres, então é rápido o suficiente” também têm outra variação
    É colocar um log de debug que dispare quando a entrada crescer muito além das restrições de projeto originais. Ele transmite quase a mesma mensagem ao futuro desenvolvedor, mas permite descobrir mais cedo e reduzir ainda mais o tempo de diagnóstico e depuração

    • Em muitos casos, é uma boa ideia. Às vezes usamos um loop que funciona no momento, mas é muito lento; daria para colocar um timer e fazer “se levar mais de X segundos, registrar um log de aviso”
      Em um sistema ideal de logging e observabilidade, mediríamos o tempo de todos os componentes do app e deixaríamos informações de debug com frequência, mas quem de fato usa um sistema perfeito desses? É mais importante fazer o esforço específico de adicionar logs nas partes em que a performance pode piorar depois ou onde não houve tempo para otimizar
      Em retrospecto, é uma ideia óbvia, mas até agora eu costumava deixar comentários nos pontos que precisariam ser revistos depois e torcer para lembrar desses comentários quando as coisas piorassem
    • Ferramentas bem-intencionadas como bazel muitas vezes tratam saídas que não são erros como ruído a ser ocultado. Elas acabam mandando essas mensagens para a lata de lixo mais próxima, então, em algumas áreas, logs se tornam um meio bastante instável de transmitir restrições
  • Não importa o que digam, uso muitos comentários e comentários de documentação por todo o código. Mas faço a abordagem inversa: primeiro escrevo, em comentários, uma lista aproximada das etapas da aplicação e, durante o desenvolvimento, vou quebrando as etapas grandes em etapas menores, apagando ou mantendo os comentários originais e refinando-os até virar um algoritmo quase completo
    Como normalmente programo de fora para dentro, também escrevo o código enquanto vou quebrando os comentários. Às vezes programo tudo de uma vez e depois comento a ponto de a maioria achar cansativo. Coloco descrições em todas as funções e variáveis, e até na função deg_to_rad ponho """Converts degrees to radians.""". Armazenamento é barato
    Sei que a maioria não gosta disso, mas tudo bem. Se não quiser ver, dá para remover com um script ou tirar na revisão de código. Ainda assim, acho muito mais agradável ler meu código antigo do que código alheio sem comentários. Em Python, código boilerplate simples como uma API Flask em geral é autodocumentado, mas às vezes é justamente esse boilerplate, por mudar bastante, que recebe comentários importantes. Na indústria, é mais comum reescrever por inteiro a parte de algoritmos
    Vou continuar gostando de comentários e comentários de documentação

    • Faço algo parecido, mas principalmente só nos componentes de nível mais alto, porque é ali que os comentários são mais úteis
      Gosto de fazer a conceitualização inteira na cabeça, e no início sinto que experimentar várias opções de projeto escrevendo de fato é lento. Então, quando uma escolha de design fica definida, ela precisa ser documentada. Afinal, outras pessoas ainda não têm acesso à minha cabeça
      Espero que os detalhes fiquem mais claros quando os outros também concluírem essa conceitualização. Mas, se por algum motivo essa conceitualização não acontecer, o código pode ficar mais difícil de ler, então faço ajustes adicionais para manter pelo menos uma legibilidade básica
    • Comentários são ótimos quando bem mantidos. Mas, exceto em casos como open source, em que há muito mais leitores do que pessoas mantendo os comentários, no fim todos esquecem ou ficam com preguiça de mantê-los
      Atualizar comentários muitas vezes dá tanto trabalho quanto corrigir o código. Então, na prática, comentários geralmente são algo esperando para virar mentira. No fim, comentário e código se desalinham, e isso pode ser pior do que código sem comentários
      Prefiro testes automatizados que mostrem a intenção. Em geral, testes não conseguem mentir, porque, se mentissem, não teriam sido mesclados. Se houver um bom conjunto de testes mostrando como o código deveria ser usado, quero vê-lo, porque ele é explicativo e quase garantidamente verdadeiro
    • Eu estava de acordo até a parte de “se não gostar, remova com um script na sua versão ou tire na revisão de código”; a partir daí, isso parece um grande sinal de alerta em um colega de equipe
    • Também sou parecido. Em cerca de metade dos casos, começo escrevendo comentários que explicam o que pretendo fazer e depois acrescento o que não saiu como esperado e o que precisei fazer para realmente funcionar
      Ajuda demais quando preciso voltar a olhar 5 semanas depois
    • Não acho que comentários de documentação sejam necessários em todos os casos, nem que seja preciso documentar todos os parâmetros e valores de retorno de todas as funções. Mas em APIs complexas eles certamente são úteis
  • Sigo a ideia de que “comentários são um pedido de desculpas ao meu eu do futuro”
    Se o código é estranho ou lento, ou se é uma parte que, ao explicar para alguém, eu diria “está meio tosco”, normalmente deixo um comentário. Especialmente se já tentei mudar aquilo antes, documento os casos que não funcionaram, o que foi corrigido etc.
    Ao abordar os comentários com esse critério, os comentários desnecessários desaparecem naturalmente, e em geral você acaba documentando o porquê só quando realmente precisa. Teste por cerca de um mês na sua própria base de código e vai entender a sensação