Quali sono gli esempi di commenti che ti dicono perché invece di come o cosa? [chiuso]

78

Prima di tutto, in questa domanda mi piacerebbe stare lontano dalla polemica sul fatto che i commenti sul codice sorgente siano buoni o cattivi. Sto solo cercando di capire più chiaramente cosa intendono le persone quando parlano di commenti che dicono WHY, WHAT o HOW.

Spesso vediamo linee guida come "I commenti dovrebbero dirti PERCHE ', il codice stesso dovrebbe dirti COME". È facile essere d'accordo con la dichiarazione a livello astratto. Tuttavia, di solito le persone abbandonano questo come un dogma e lasciano la stanza senza ulteriori spiegazioni. Ho visto questo usato in così tanti luoghi e contesti diversi, che sembra che le persone possano essere d'accordo con lo slogan, ma sembrano parlare di cose completamente diverse.

Quindi, tornando alla domanda: se i commenti dovessero dirti PERCHE ', cos'è questo PERCHE' stiamo parlando? È questo il motivo per cui quel pezzo di codice esiste in primo luogo? E 'questo quello che dovrebbe fare il codice pezzo? Apprezzerei molto se qualcuno potesse dare una spiegazione chiara, e quindi aggiungere alcuni buoni esempi (gli esempi cattivi non sono realmente necessari, ma sono stati liberi di aggiungerli per contrasto).

Ci sono molte domande sul fatto che i commenti siano buoni o cattivi, ma nessuno che risponda alla domanda specifica di quali sono buoni esempi di commenti che dicono PERCHÉ.

    
posta rick 09.08.2013 - 08:51
fonte

13 risposte

62

L'esempio più comune e più caratteristico sono i commenti su varie soluzioni alternative. Ad esempio questo:

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"

Troverai sicuramente più esempi nelle fonti Git e Linux; entrambi i progetti cercano di seguire questa regola.

Raccomando anche di seguire questa regola ancora più strettamente con commit logs . Per i commenti al codice può capitare di correggere il codice, ma dimenticarsi di aggiornare il commento. Con la quantità di codice nel solito progetto, è garantito che succeda prima o poi. D'altra parte il log di commit è legato alla particolare modifica e può essere richiamato utilizzando la funzionalità "annotate" / "blame" del sistema di controllo della versione. Ancora una volta Git e Linux hanno dei buoni esempi.

Guarda per es. all'indirizzo questo commit . (non copiare qui, è troppo lungo). Ha quattro paragrafi che occupano quasi tutta la pagina (e un po 'troppo schermato) che descrive cosa esattamente era sbagliato e perché era sbagliato e poi va avanti e modifica tutte le linee SIX. Usano commenti come questo per due scopi:

  1. Tutte le modifiche inviate vengono esaminate e il registro di commit è ciò che deve spiegare la modifica al revisore.
  2. Quando viene rilevato un errore, i log rilevanti vengono recuperati utilizzando "pickaxe" o "blame" per evitare di ripristinare in precedenza anche un comportamento scorretto.

(nota: ci sono voluti al massimo 10 minuti di ricerca casuale del repository git per trovare questi due esempi, quindi sarebbe sicuramente più facile trovarne altri)

    
risposta data 09.08.2013 - 09:52
fonte
29

Un commento che ti dice perché spiega il ragionamento alla base del codice, ad esempio:

// 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;
    }
}

Un commento che ti dice come spiega che cosa fa il codice.

// 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;
    }
}

La differenza è che un manutentore può guardare il primo e dire "Oh, quindi potrebbe non essere aggiornato!" Nel secondo caso, il maintainer ha un commento che non ti dice nulla che il codice stesso non rivela (assumendo nomi di variabili validi).

Ecco un esempio di vita reale di un commento perché, da un codice iOS su cui ho lavorato, dovevamo ottenere un indirizzo gateway (o un'ipotesi ragionevole). Avrei potuto lasciare i commenti che dicevano cose come "Inizializza il socket di ricezione", ma questo direbbe solo a un maintainer (o futuro me) cosa stava succedendo, non perché ho dovuto fare questo strano kludge per ottenere l'indirizzo del gateway nel primo posto.

/*
 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
    
risposta data 09.08.2013 - 14:58
fonte
18

Vorrei iniziare la mia risposta con una citazione fatta da Jeff Atwood nel suo post sul blog Il codice ti dice come, i commenti ti dicono perché :

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

Dichiara anche che:

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.

Sono assolutamente d'accordo e a questo punto devo aggiungere che prima di poter iniziare a rendere il codice il più semplice possibile, faccio in modo che il codice funzioni e poi inizi a rifattorizzare. Quindi durante la prima corsa prima di refactoring aggiungere perché i commenti aiutano molto.

Ad esempio se si utilizzano 3 cicli nidificati con 2 hash dimensionali per riempire una tabella dei giorni feriali mentre si analizzano i dati, è molto facile perdere traccia di ciò che è stato fatto da qualcuno o anche da soli se non è stato guardato per alcune settimane e improvvisamente refactoring .

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

La tomaia è un esempio di come funzionano 3 cicli annidati prima del refactoring.
Inoltre, la spiegazione di alcune condizioni del ramo può aiutare a capire il codice molto meglio con ciò che si stava pensando nel 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); 
}

Anche il codice semplice e ovvio funziona bene con i commenti. Solo per rendere le cose un po 'più ovvie, più chiare o più facili da capire per i colleghi e anche per te stesso nel mantenimento del software.

Certo che xp afferma di avere un codice che si spiega da sé, ma fa male un commento su una riga?

Trovo anche le seguenti regole da questo blog essere molto utile:

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

Chiunque debba tornare nel proprio codice o in qualcun altro o addirittura in un codice legacy sa che può essere un mal di testa. Quindi, invece di essere pigri o cercare di essere un uber-coder nel non commentare nulla o molto poco, perché non creare il proprio o un bugger povero, che deve mantenere il proprio codice, la vita futura è molto più facile seguendo le regole citate.

Anche molte descrizioni di programmazione fatte sono messe in dubbio durante le recensioni e non è sempre chiaro il motivo per cui alcune parti sono state scritte come erano anche se alcune sezioni di codice sono vitali per un programma per funzionare a causa di un grosso bug trovato mentre il codice era usato negli anni. Quindi, per non annoiarvi completamente con un tl; dr close con un'ultima citazione da 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.

    
risposta data 09.08.2013 - 09:45
fonte
8

Tendo a ridurre i commenti a entrambi i riferimenti in cui una determinata funzionalità / codice viene spiegata in modo più approfondito o per spiegare perché viene scelto un determinato modo di programmazione.

Considerando che altri programmatori con competenze simili usano o leggono il tuo codice, è importante commentare se usi un modo diverso dal previsto per ottenere qualcosa. Quindi puoi spiegare in un commento perché scegli in questo modo.

Ad esempio, se puoi utilizzare due sensori diversi su un dispositivo Android e uno di questi non soddisfa le tue esigenze, puoi spiegare nel commento perché hai scelto l'altro.

Quindi il 'perché' dovrebbe dare una logica alle tue scelte fatte.

    
risposta data 09.08.2013 - 09:19
fonte
8

I commenti dovrebbero dirti quale sia il codice, non necessariamente delineato da WHY , HOW o WHAT . Se hai un buon nome e hai funzioni ben delineate, è abbastanza probabile che il codice possa dirti esattamente cosa sta succedendo. Ad esempio:

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);
}

Questo codice in realtà non ha bisogno di commenti. I nomi di funzioni e tipi lo rendono facile da capire.

A volte, tuttavia, può essere difficile o impossibile realizzare codice scorrevole come sopra. Ad esempio, il prossimo snippet di codice serve a trovare un punto statisticamente casuale su una sfera. La matematica è piuttosto opaca, quindi un commento con un link alla spiegazione è di aiutare a dire COME funziona. Questo può essere racchiuso in una funzione per dire a CHE COSA lo fa senza richiedere un commento se necessario più di una volta, altrimenti il titolo del link aiuta anche in quel dipartimento.

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));

Un altro esempio di quando i commenti ti dicono che cosa il codice non è per spiegare una decisione. Nel prossimo esempio, il codice non blocca una variabile locale non thread all'interno di una parte di codice thread. C'è una ragione per questo e il commento spiega PERCHÉ . Senza il commento, potrebbe essere considerato un bug, o semplicemente non essere nemmeno notato.

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)
    {
        ...
    }
    ...
}

Potrebbe, forse, essere migliorato per dire perché l'oggetto casuale non viene creato all'interno del ciclo parallelo in primo luogo. Se non c'è motivo, potrebbe anche far venire qualcuno e capire che l'intera idea è stupida ed è un buon posto per il refactoring.

    
risposta data 09.08.2013 - 09:45
fonte
5

Potrebbe essere utile riconoscere diversi tipi di "perché", in particolare:

  • Ragiona che il codice che sembra eccessivamente complesso non funzionerebbe se semplificato (ad esempio un typecast apparentemente superfluo potrebbe essere necessario per garantire che il codice funzioni in alcuni casi angolari).

  • Motivi per cui alcune semplici operazioni che sembrano pericolose sono effettivamente sicure (ad es. "La nostra routine di recupero dei dati riporterà una voce fittizia oltre l'ultima ad essere meno di ogni altra cosa, e l'articolo dopo sarà maggiore ; ogni elemento che deve essere ordinato prima di un altro, in una sequenza crescente ascendente o discendente, avrà almeno un altro oggetto (eventualmente fittizio) che lo segue ").

In molti casi, un commento del secondo tipo in una parte del codice può "corrispondere" con un commento del primo tipo in un altro (ad esempio "Mentre sembrerebbe che questa sequenza di operazioni possa essere semplificata, il Fitz la routine si basa sul fatto che Wongle non viene Woozled fino a dopo che Bandersnatch è stato Blarped. ")

    
risposta data 09.08.2013 - 18:03
fonte
2

Non dimenticare, se stai scrivendo un programma, non stai scrivendo casualmente roba, lo stai facendo perché hai un modello di ciò che vuoi , sia che sia in un documento formale o solo nella tua testa. La roba nella tua mente è reale tanto quanto il software / i dati in un computer (e altrettanto facilmente contenere bug).

Qualcuno che legge il tuo codice potrebbe non avere quel modello in testa, quindi i commenti possono servire a dire loro che cos'è il modello e in che modo il codice si relaziona con esso. Penso che sia ciò che si intende per "perché". Certamente è bello rendere il codice stesso il più autoesplicativo possibile, ma non sempre è abbastanza buono. Esempio:

// 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);

Inoltre, il modello cambia nel tempo e tali modifiche devono essere trasferite al codice. Quindi i commenti devono non solo dire "perché" c'è qualcosa nel codice, ma altrettanto importante come cambiarlo in risposta ai cambiamenti del modello previsti. Esempio:

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

Penso che lo scopo dei commenti sia a volte trascurato.

    
risposta data 09.08.2013 - 14:11
fonte
2

Non tutti i miei commenti sono del tipo "perché", ma molti lo sono.
Questi sono esempi da un file sorgente (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.

Nota che (i miei) perché i commenti di solito precedono il codice che sta per farlo (quindi terminano con due punti).

Alcuni commentano spiegando solo che cosa sta succedendo , ad es. quando un processo ha molti passaggi che hanno un raggruppamento logico (e il codice non è refactored per mostrarlo automaticamente), commenterò come:

// Step 1. Initialization
    
risposta data 09.08.2013 - 09:13
fonte
1

Comprendo il motivo PERCHÉ è il motivo per cui fai qualcosa in un modo forse strano o forse illogico, a causa delle circostanze che lo richiedono. Il HOW può essere visto nel codice stesso, indipendentemente da quanto sia strano, anche se il codice non ha "senso". Il WHAT è probabilmente meglio descritto all'inizio della documentazione di classe / funzione. Quindi questo ti lascia con l'aggiunta del WHY , in cui spieghi tutto ciò che non è incluso in HOW e WHAT, e i modi particolari che devi seguire per motivi indipendenti dal tuo controllo.

Naturalmente non è sempre il caso, al di fuori della terra degli unicorni e degli arcobaleni ...

COME:

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

CHE COSA:

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

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

WHY:

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

Ho imparato SEMPRE a scrivere commenti in file header C ++ (poiché non è sempre chiaro COSA una funzione fa, anche se il nome dà un buon suggerimento) specialmente se si passa un'API ad altri sviluppatori o si usa uno strumento per autodoc come doxygen.

Quindi per me un commento tipico assomiglia a

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

L'unica volta che ho usato i commenti WHY è roba che è difficile da afferrare e talvolta persino dal programmatore, come "NON TOCCARE QUESTO! Perché ..." o "PROGRAMMARE CRASH SE LA LINEA È CANCELLATA ..."

Soluzioni alternative, hack e strani comportamenti si adattano ai criteri WHY ai miei occhi ...

Un esempio molto buono e persino esilarante è questa "soluzione" per qualche codice incasinato scritto da una persona di nome Richard, qualcun altro lo ha incartato e ha spiegato perché nei commenti ... link

Purtroppo ci sono diverse volte in cui sei costretto a fasciare il toro **** perché non puoi toccare l'originale, perché "è sempre stato così" o non hai accesso o ... beh, non hai il tempo di sistemare l'originale per lo scopo in realtà non si qualifica per l'overhead.

    
risposta data 09.08.2013 - 13:17
fonte
0

Il codice dovrebbe specificare il piano di esecuzione. In questo modo il programma follower (o il compilatore) può capire cosa fare e come farlo. Ciò che è suddiviso in passaggi che il seguace del programma può seguire. I passi primitivi sono il modo.

L'intento del programmatore è un'altra questione. In un codice semplice, chiaro e diretto l'intento è ovvio. Qualsiasi lettore umano ragionevolmente esperto arriverà all'intento di un blocco di codice, semplicemente leggendo il codice. La maggior parte del codice dovrebbe leggere in questo modo.

Occasionalmente, la relazione tra intenzione e piano è oscura. Il codice rivela il cosa e il come, ma non il perché. Ecco quando valgono i commenti che rivelano l'intento. L'intento del programmatore è il perché.

    
risposta data 09.08.2013 - 13:31
fonte
0

Avendo questo problema in questo momento guado attraverso stored procedure e viste contro un modello di dati complesso e un po 'contorto.

Abbiamo (numerosi) elementi selezionati come "Caso quando x.account non è nullo e x.indirizzo in (seleziona indirizzo da fedex) poi x.account altro y.account fine" in tutto e la produttività è prevista anche se c'è non ha affatto tempo di leggere tutto il codice sorgente. E questo tipo di sorta di sorta ha senso, ma è ancora imperscrutabile.

I commenti che spiegano il motivo per cui se in fedex poi x e in caso contrario y - getta luce sull'intero sistema e quando ne leggiamo abbastanza ne iniziamo a prenderlo. E questo è fin troppo semplificato e ci sono centinaia o migliaia di affermazioni simili. Il mio cuore emette un bagliore caloroso nei confronti di chiunque sia stato il dev di tipo dal 2007 a inserire chi è.

Quindi sì, complessi modelli di dati complessi e linee pelose e stored procedure con più percorsi validati, per favore, per amore di G-d, dicci perché.

    
risposta data 09.08.2013 - 22:33
fonte
0

Ho appena scritto questo commento; è un esempio concreto di spiegare perché una linea di codice è quella che è, e in particolare perché l'ho cambiata.

Il metodo esamina i dati memorizzati e valuta se è completo fino al giorno presente da un lato e attraverso la data di inizio dall'altro capo.

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

Come si può probabilmente intuire, l'operatore più grande di era stato un maggiore o uguale. Il commento spiega perché il vecchio valore ha senso e perché il nuovo valore è migliore. Se qualcuno guarda questo in futuro, vedrà che l'uso di ">" non è una svista, ma un'ottimizzazione. Possono quindi cambiarlo o lasciarlo, in base alla necessità in quel momento.

    
risposta data 15.08.2013 - 17:56
fonte

Leggi altre domande sui tag