Quais são os exemplos de comentários que informam por que, em vez de como ou o quê? [fechadas]

78

Em primeiro lugar, nesta questão, gostaria de ficar longe da polêmica sobre se o código-fonte comentando é bom ou ruim. Eu só estou tentando entender mais claramente o que as pessoas querem dizer quando falam sobre comentários que dizem por que, o que ou como.

Frequentemente, vemos diretrizes como "Os comentários devem informar PORQUÊ: o próprio código deve informar COMO". É fácil concordar com a afirmação em um nível abstrato. No entanto, as pessoas geralmente deixam isso como um dogma e saem da sala sem maiores explicações. Eu vi isso usado em muitos lugares e contextos diferentes, que parece que as pessoas podem concordar com o slogan, mas eles parecem estar falando sobre coisas completamente diferentes.

Então, voltando à pergunta: se os comentários devem lhe dizer POR QUE, do que estamos falando? Esta é a razão pela qual esse pedaço de código existe em primeiro lugar? É isso que esse código de peça deveria estar fazendo? Eu realmente apreciaria se alguém pudesse dar uma explicação clara e, em seguida, adicionar alguns bons exemplos (exemplos ruins não são realmente necessários, mas fique à vontade para adicioná-los ao contraste).

Existem muitas perguntas sobre se os comentários são bons ou ruins, mas ninguém aborda a questão específica de quais são bons exemplos de comentários que informam o motivo POR QUÊ.

    
por rick 09.08.2013 / 08:51
fonte

13 respostas

62

O exemplo mais comum e mais distinto é o comentário em torno de várias soluções alternativas. Por exemplo, este:

https://github.com/git/git/blob/master/compat/fopen.c:

/*
 *  The order of the following two lines is important.
 *
 *  FREAD_READS_DIRECTORIES is undefined before including git-compat-util.h
 *  to avoid the redefinition of fopen within git-compat-util.h. This is
 *  necessary since fopen is a macro on some platforms which may be set
 *  based on compiler options. For example, on AIX fopen is set to fopen64
 *  when _LARGE_FILES is defined. The previous technique of merely undefining
 *  fopen after including git-compat-util.h is inadequate in this case.
 */
#undef FREAD_READS_DIRECTORIES
#include "../git-compat-util.h"

Você certamente encontrará mais exemplos em fontes Git e Linux; ambos os projetos tentam seguir essa regra.

Eu também recomendo seguir esta regra ainda mais estritamente com logs de commit . Para comentários de código, pode acontecer que você corrija o código, mas esqueça de atualizar o comentário. Com a quantidade de código no projeto usual, é garantido que isso aconteça mais cedo ou mais tarde. Por outro lado, o log de confirmação está vinculado à alteração específica e pode ser recuperado usando a funcionalidade "anotar" / "culpar" do sistema de controle de versão. Novamente o Git e o Linux têm alguns bons exemplos.

Veja por exemplo em este commit . (não copiando aqui, é muito longo). Ele tem quatro parágrafos que ocupam quase toda a página (e um pouco sobre a tela), descrevendo o que exatamente estava errado e por que estava errado e que continua e modifica todas as linhas SIXs. Eles usam comentários como esse para dois propósitos:

  1. Todas as alterações enviadas são revisadas e o log de confirmação é o que deve explicar a alteração ao revisor.
  2. Quando um bug é encontrado, os registros relevantes são recuperados usando "pickaxe" ou "blame" para evitar reverter para um comportamento anterior também incorreto.

(nota: levei 10 minutos de navegação aleatória do repositório do git para obter estes dois exemplos, então seria mais fácil encontrar mais lá)

    
por 09.08.2013 / 09:52
fonte
29

Um comentário que diz a você por que explica o raciocínio por trás do código - por exemplo:

// We need to sync the values if the temp <doodad> GUID matches one of the active <doodad>'s
// GUID, as the temp <doodad> has the most recent values according to the server and said 
// values might have changed since we added the <doodad>. We want a user to be able to <foo> 
// the <doodad> whenever, which means those values must be accurate.
for (doodad in doodads) {
    if ([doodad guid] == [tempDoodad guid]) {
        [doodad updateFromDoodad:tempDoodad];
        break;
    }
}

Um comentário que diz a você como explica o que o código faz.

// Loop through our <doodads> and check for a GUID match. If it matches, copy the new values
// on the <doodad> that matches 
for (doodad in doodads) {
    if ([doodad guid] == [tempDoodad guid]) {
        [doodad updateFromDoodad:tempDoodad];
        break;
    }
}

A diferença é que um mantenedor pode olhar o primeiro e dizer: "Ah, isso pode estar desatualizado!" No segundo caso, o dito mantenedor tem um comentário que não diz nada que o código não revela (assumindo bons nomes de variáveis).

Aqui está um exemplo da vida real de um comentário do porquê, de algum código iOS em que trabalhei, onde precisávamos para obter um endereço de gateway (ou palpite razoável para ele). Eu poderia ter acabado de deixar os comentários que diziam coisas como "Inicializar o socket de recepção", mas isso só diria a um mantenedor (ou futuro eu) o que estava acontecendo, não por que eu tive que fazer esse estranho kludge para obter o endereço do gateway primeiro lugar.

/*
 We're going to do something really hacky here and use a custom partial
 implementation of traceroute to get our gateway IP address.

 [rant removed - irrelevant to the point]

 There's no good way to get at the gateway address of an iDevice
 right now. So, we have two options (per https://devforums.apple.com/message/644915#644915 ):
 1. Get at and parse the routing table (like netstat -rn, or route -n)
 2. Do a traceroute and grab the IP address for the first hop

 As far as I can tell, the former requires <sys/route.h> from the Mac OS X
 header files, which doesn't seem like a good idea to me. Also, there's a
 thread on the Apple Developer forums that seems to imply that header isn't
 in iOS for a reason (https://devforums.apple.com/message/774731#774731 ).

 So when we send our request with a TTL of one it will survive a single hop
 to the router and return, triumphant, with the router's IP address!

 Viva la kludge!

 PS: Original source was the below SO question, but I've modded it since then.
 http://stackoverflow.com/questions/14304581/hops-tracing-ttl-reciveform-on-ios/14304923#14304923
 */

// Default to using Google's DNS address. We used to try checking www.google.com
// if reachability reported we had internet, but that could still hang on routers
// that had no internet connectivity - not sure why.
const char *ip_addr = [kGoogleDNS UTF8String]; // Must be const to avoid undefined behavior
struct sockaddr_in destination,fromAddr;
int recv_sock;
int send_sock;

// ... more code follows
    
por 09.08.2013 / 14:58
fonte
18

Gostaria de começar minha resposta com uma citação feita por Jeff Atwood em sua postagem no Blog. Código diz a você como, comentários dizem por que :

the best kind of comments are the ones you don't need

Ele também afirma que:

You should first strive to make your code as simple as possible to understand without relying on comments as a crutch. Only at the point where the code cannot be made easier to understand should you begin to add comments.

Concordo totalmente e, neste ponto, devo acrescentar que, antes de poder começar a tornar o código o mais simples possível, faço o código funcionar e, em seguida, inicio a refatoração. Então, durante a primeira corrida antes de refatorar, adicionar por que os comentários ajudam muito.

Por exemplo, se usar 3 loops aninhados com hashtabens bidimensionais para preencher uma tabela de dias úteis durante a análise de dados, é muito fácil perder o controle do que foi feito por alguém ou mesmo por você mesmo se não for observado por algumas semanas e repactar subitamente .

[loop1]6oclock -> [loop2]Monday -> [loop3]stage 1 to 4
         -> tuesday-> stage 1 to 4
         ...
         -> Saturday -> stage 1 to 4
    7oclock -> Monday-> stage 1 to 4
        ....etc.

A parte superior é um exemplo de como 3 loops aninhados funcionariam antes da refatoração.
Também explicar algumas condições de ramificação pode ajudar a entender melhor o código com o que se estava pensando no processo:

// added a zero before the actual day in order for the days always to be 2 digits long.
if( actualDayFuture < 10 ) 
{ 
     actualDayFuture = padIfSingleDigitDate(actualDayFuture); 
}

Mesmo códigos simples e óbvios funcionam bem com comentários. Apenas para tornar as coisas um pouco mais óbvias, mais claras ou mais fáceis de entender para os colegas e até mesmo para você mesmo na manutenção do software.

O xp afirma que o código é auto-explicativo, mas um comentário de uma linha prejudica?

Eu também acho as seguintes regras deste blog ser muito útil:

  • Understand the material before you write
  • Write as though your audience is a fourth grader
  • Think about how readers might misinterpret you

Qualquer um que tenha que voltar ao seu próprio código ou a outras pessoas ou até mesmo código legado sabe que pode ser uma dor de cabeça. Então, em vez de ser preguiçoso ou tentar ser um uber-codificador em não comentar nada ou muito pouco, por que não fazer o seu próprio ou algum pobre coitado, que tem que manter seu código, a vida futura muito mais fácil seguindo as regras citadas.

Também muitas descrições de programação feitas são duvidadas durante as revisões e nem sempre está claro por que algumas partes foram escritas como eram, mesmo se algumas seções de código são vitais para um programa funcionar devido a um grande bug encontrado como o código foi usado ao longo dos anos. Então, para não aborrecê-lo completamente com um dr dr fechar com uma última citação de acmqueue :

Prior, clear, and extensive documentation is a key element in creating software that can survive and adapt. Documenting to high standards will decrease development time, result in better work, and improve the bottom line. It’s hard to ask for more than that from any technique.

    
por 09.08.2013 / 09:45
fonte
8

Eu tenho a tendência de reduzir os comentários para as referências onde uma determinada funcionalidade / código é explicada mais detalhadamente ou para explicar por que uma determinada forma de programação é escolhida.

Considerando que outros programadores com habilidades semelhantes usam ou lêem seu código, é importante comentar se você usa uma maneira diferente do esperado de conseguir algo. Então você pode explicar em um comentário por que você escolhe dessa maneira.

Por exemplo, se você puder usar dois sensores diferentes em um dispositivo Android e um deles não atender às suas necessidades, você poderá explicar no comentário por que escolheu o outro.

Assim, o "porquê" deve dar um raciocínio às suas escolhas feitas.

    
por 09.08.2013 / 09:19
fonte
8

Os comentários devem informar o que o código não inclui, não necessariamente deliniated por WHY , COMO ou WHAT . Se você tiver bons nomes e tiver funções bem delineadas, é bem possível que o código possa dizer exatamente o que está acontecendo. Por exemplo:

List<LightMap> maps = makeLightmaps(receivingModels);
TrianglePartitioner partition = new Octree(castingTriangles);
List<Photon> photons = firePhotons(lights, partition);

if (photons.Count > 0)
{
      PhotonPartitioner photonMap = new KDTree(photons);
      gatherPhotons(maps, photonMap, partition, lights);
}

Esse código realmente não precisa de comentários. Os nomes de função e tipo facilitam a compreensão.

Às vezes, no entanto, pode ser difícil ou impossível criar um código fluente como o acima. Por exemplo, o próximo trecho de código é para encontrar um ponto estatisticamente aleatório em uma esfera. A matemática é bastante opaca, então um comentário com um link para a explicação é para ajudar a dizer COMO funciona. Isso pode ser agrupado em uma função para informar O QUE ele faz sem precisar de um comentário, se necessário, mais de uma vez; caso contrário, o título do link também ajudará nesse departamento.

double randomA = localGenerator.NextDouble();
double randomB = localGenerator.NextDouble();

//http://mathworld.wolfram.com/SpherePointPicking.html
double theta = 2 * Math.PI * randomA;
double phi = Math.Acos(2 * randomB - 1);

Vector3 randomDirection = new Vector3(Settings.ambientRayLength * (float)(Math.Cos(theta) * Math.Sin(phi)),
                                      Settings.ambientRayLength * (float)(Math.Sin(theta) * Math.Sin(phi)),
                                      Settings.ambientRayLength * (float)Math.Cos(phi));

Outro exemplo de quando os comentários informam o que o código não significa é explicar uma decisão. No próximo exemplo, o código não bloqueia uma variável não thread-local dentro de um pedaço de código encadeado. Há uma razão para isso e o comentário explica POR QUE . Sem o comentário, pode ser considerado um bug ou simplesmente não ser notado.

Random random = new Random();
Parallel.For(0, maxPhotons, delegate(int photonIndex, ParallelLoopState state)
{
    ...
    //I don't actually care if this random number is unique between threads, threadsafty is not that big of a deal
    //  in this case and locking the random object could cause a lot of lock contention
    while (random.NextDouble() > reflectProbability)
    {
        ...
    }
    ...
}

Poderia, talvez, ser melhorado dizer por que o objeto aleatório não é criado dentro do loop paralelo em primeiro lugar. Se não houver razão, também pode fazer alguém aparecer e perceber que a ideia é estúpida e é um bom lugar para refatorar.

    
por 09.08.2013 / 09:45
fonte
5

Pode ser útil reconhecer diferentes tipos de "por quê" - principalmente:

  • Razões que o código que parece excessivamente complexo não funcionaria se fosse simplificado (por exemplo, um typecast aparentemente supérfluo pode ser necessário para garantir que o código funcione em alguns casos específicos).

  • Razões que algumas operações simples que parecem perigosas são realmente seguras (por exemplo: "Nossa rotina de busca de dados relatará um item de item simulado sendo o último como sendo menor que qualquer outra coisa, e o item depois disso como sendo maior qualquer item que deve classificar antes de outro, em uma seqüência ascendente ou descendente consistente, terá pelo menos mais um item (possivelmente falso) seguindo-o ").

Em muitos casos, um comentário do segundo tipo em uma parte do código pode "corresponder" com um comentário do primeiro tipo em outro (por exemplo, "Embora parecesse que essa sequência de operações poderia ser simplificada, o Fitz A rotina confia em que o Wongle não seja Woozled até que o Bandersnatch tenha sido Blarped. ")

    
por 09.08.2013 / 18:03
fonte
2

Não se esqueça, se você está escrevendo um programa, você não está apenas escrevendo coisas aleatoriamente, você está fazendo isso porque você tem um modelo do que você quer , seja em um documento formal ou apenas na sua cabeça. Coisas em sua cabeça são tão reais quanto software / dados em um computador (e provavelmente contêm bugs).

Alguém que lê seu código pode não ter esse modelo em mente, portanto, os comentários podem servir para dizer a eles qual era o modelo e como o código se relaciona com ele. Eu acho que é isso que significa "por que". Certamente é bom tornar o código tão auto-explicativo quanto possível, mas isso nem sempre é bom o suficiente. Exemplo:

// transform the x,y point location to the nearest hexagonal cell location
ix1 = (int)floor(0.5 + x + y/2);
iy1 = (int)floor(0.5 + y);

Além disso, o modelo muda com o tempo e essas alterações precisam ser transferidas para o código. Portanto, os comentários precisam não apenas dizer "por que" algo está no código, mas também tão importante como alterá-lo em resposta às mudanças previstas no modelo. Exemplo:

// to change to square cell locations, remove the "+ y/2" in the above code

Eu acho que o propósito dos comentários é às vezes negligenciado.

    
por 09.08.2013 / 14:11
fonte
2

Nem todos os meus comentários são do tipo "por que", mas muitos são.
Estes são exemplos de um arquivo fonte (Delphi):

// For easier access to the custom properties:

function GetPrivate: Integer;   // It's an integer field in the external program so let's treat it like that here

// The below properties depend on the ones above or are calculated fields.
// They are kept up-to-date in the OnEventModified event of the TTSynchronizerStorage
// or in the ClientDataSet.OnCalcFields of the TcxDBSchedulerStorage.DataSource.DataSet
property IsModified       : Boolean   read GetIsModified   write SetIsModified;
property IsCatTT          : Boolean   read GetIsCatTT      write SetIsCatTT;
property IsSynced         : Boolean   read GetIsSynced     write SetIsSynced;

lLeftPos := pos(' - [',ASubject); // Were subject and [shiftnaam:act,project,cust] concatenated with a dash?

// Things that were added behing the ] we will append to the subject:

// In the storage the custom value must also be set for:
Self.SetCustomFieldValueByname(cCustFldIsCatTT,Result);

// When we show the custom fields in a grid, the Getters are not executed,
// because the DevEx code does not know about our class helpers.
// So we have two keep both properties synchronized ourselves:

// lNewMasterEvent was set to usUpdated, overwrite because we added:
if ARepair then
  lNewMasterEvent.CustUpdateStatus := usRecreated

// The source occurrence date may have bee changed. Using GetOriginalDate we can retrieve the original date,
// then use that for creating a target occurrence (and update its date):

lNewTTOccurrence.CustSyncEntryID := cSyncEntryID0;    // Backward compatibility with old sync methode

// Single event became recurring or vice versa; replace entire event

// In contradiction to CopySingleEventToTimeTell, CopyMasterEventToTimeTell does not have a ANewStatus parameter
// because master events are always added.

Note que (meu) por que os comentários geralmente precedem o código que irá fazê-lo (daí terminar com dois pontos).

Eu faço alguns comentários explicando apenas o que está acontecendo, por exemplo Quando um processo tem muitas etapas que têm um agrupamento lógico (e o código não é refatorado para mostrar isso automaticamente), vou comentar como:

// Step 1. Initialization
    
por 09.08.2013 / 09:13
fonte
1

Eu entendo o PORQUE como sendo a razão pela qual você faz algo de uma forma possivelmente estranha ou talvez ilógica, devido às circunstâncias dadas que exigem que isso seja feito. O HOW pode ser visto no próprio código, não importa o quão estranho seja, mesmo que o código não tenha "sentido". O WHAT é provavelmente melhor contado no início da documentação da classe / função. Isso deixa você com o WHY , onde você explica qualquer coisa que não esteja incluída no HOW e WHAT, e as maneiras peculiares que você precisa tomar devido a razões além do seu controle.

Claro que nem sempre é o caso, fora da terra dos unicórnios e arco-íris ...

COMO:

foreach($critters as $creature) {
   $creature->dance();
}

O QUE:

/* Dancing creatures v1.0
 * 
 * The purpose of this is to make all your critters do the funky dance.
 */

foreach($critters as $creature) {
  $creature->dance();
}

PORQUE:

// We had to store the items in an array of objects because of _____ (reason)
foreach($critters as $creature) {
   $creature->dance();
}
    
por 09.08.2013 / 10:22
fonte
1

Eu aprendi a SEMPRE escrever comentários em headerfiles C ++ (já que nem sempre é claro o que uma função faz, mesmo que o nome dê uma boa dica) especialmente se você passar uma API para outros desenvolvedores ou usar uma ferramenta para autodoc como doxygen.

Então, para mim, um comentário típico parece com algo como

/*** Functionname
/*   What happens here
/*  [in] Params
/*  [out] params
/*** 

A única vez que usei os comentários do WHY é algo que é difícil de entender e às vezes até para o programador, como "NÃO TOQUE NESSE! Porque ..." ou "O PROGRAMA DERRUBARÁ SE A LINHA ESTIVER APAGADA ..."

Soluções alternativas, hacks e comportamentos estranhos qualificam-se para os critérios WHY aos meus olhos ...

Um exemplo muito bom e até hilariante é essa "solução" para algum código bagunçado escrito por uma pessoa chamada Richard, outra pessoa o envolveu e explicou por que nos comentários ... link

Infelizmente, existem algumas vezes, onde você é forçado a quebrar o touro porque você não pode tocar o original, porque "sempre foi assim" ou você não tem acesso ou ... bem, você não tem tempo para consertar o original com o propósito de realmente não se qualificar para a sobrecarga.

    
por 09.08.2013 / 13:17
fonte
0

O código deve especificar o plano de execução. Dessa forma, o programa seguidor (ou o compilador) pode descobrir o que fazer e como fazê-lo. O que é dividido em etapas que o programa seguidor pode seguir. Os passos primitivos são o como.

A intenção do codificador é outra questão. Em um código simples, claro e direto, a intenção é óbvia. Qualquer leitor humano razoavelmente proficiente chegará à intenção de um bloco de código, apenas lendo o código. A maioria dos códigos deve ser assim.

Ocasionalmente, a relação entre intenção e plano é obscura. O código revela o que e como, mas não o porquê. É quando os comentários que revelam a intenção valem a pena. A intenção do programador é o porquê.

    
por 09.08.2013 / 13:31
fonte
0

Tendo esse problema agora, percorrendo procedimentos e exibições armazenados em um modelo de dados complexo e um tanto complicado.

Nós selecionamos (numerosos) selects como "Case quando x.account não é null e x.address em (select address from fedex) então x.account else y.account end" ao longo e produtividade é esperada embora haja não é hora de todo para ler todo o código-fonte. E esse tipo de exemplo faz sentido, mas ainda é inescrutável.

Os comentários explicam por que se em fedex, em seguida, xe se não, então, y - ilumina todo o sistema e quando lemos o suficiente deles, começamos a obtê-lo. E isso é mais simplificado e há centenas ou milhares de declarações semelhantes. Meu coração brilha calorosamente em relação a quem quer que o devedor de 2007 foi quem colocou esses por que.

Então, complexos modelos de dados complicados, veiws cabeludos e procedimentos armazenados com vários caminhos com nomes válidos, por favor, o amor de D'us nos diz o porquê.

    
por 09.08.2013 / 22:33
fonte
0

Acabei de escrever este comentário; é um exemplo concreto de explicar porque uma linha de código é o que é e, em particular, por que eu a alterei.

O método examina os dados armazenados e avalia se está completo até os dias de hoje e até a data de início no outro lado.

// In principal, this should be ">=", as we may have data up to the account start
// date but not complete for that day; in practice, 98% of the time if we have
// data for the start date it *is* complete, and requerying it would be a waste
// of time.
while (endDate > accountStartDate)
    ...

Como você provavelmente pode adivinhar, o operador maior que tinha sido maior ou igual. O comentário explica porque o valor antigo faz sentido e porque o novo valor é melhor. Se alguém olhar para isso no futuro, verá que o uso de ">" não é um descuido, mas uma otimização. Eles podem então alterá-lo ou deixá-lo, com base na necessidade naquele momento.

    
por 15.08.2013 / 17:56
fonte