¿Cuáles son ejemplos de comentarios que te dicen por qué en lugar de cómo o qué? [cerrado]

78

En primer lugar, en esta pregunta me gustaría mantenerme alejado de la polémica sobre si el comentario del código fuente es bueno o malo. Solo trato de entender más claramente lo que quiere decir la gente cuando hablan sobre comentarios que te dicen POR QUÉ, QUÉ o CÓMO.

A menudo vemos pautas como "Los comentarios deben indicarle POR QUÉ; el código en sí mismo debe indicarle CÓMO". Es fácil estar de acuerdo con la declaración en un nivel abstracto. Sin embargo, la gente suele dejar esto como un dogma y dejar la habitación sin más explicaciones. He visto esto usado en tantos lugares y contextos diferentes, que parece que las personas pueden estar de acuerdo con el eslogan, pero parecen estar hablando de cosas completamente diferentes.

Entonces, volvamos a la pregunta: si los comentarios te dicen POR QUÉ, ¿de qué estamos hablando? ¿Es esta la razón por la cual ese pedazo de código existe en primer lugar? ¿Es esto lo que el código de la pieza debería estar haciendo? Realmente apreciaría si alguien pudiera dar una explicación clara, y luego agregar algunos buenos ejemplos (los malos ejemplos no son realmente necesarios, pero se puede agregarlos como contraste).

Hay muchas preguntas sobre si los comentarios son buenos o malos, pero nadie aborda la pregunta específica de cuáles son los buenos ejemplos de comentarios que te dicen POR QUÉ.

    
pregunta rick 09.08.2013 - 08:51

13 respuestas

62

El ejemplo más común y distintivo son los comentarios sobre varias soluciones. Por ejemplo este:

  

enlace :

/*
 *  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"

Seguramente encontrarás más ejemplos en las fuentes de Git y Linux; Ambos proyectos intentan seguir esta regla.

También recomiendo seguir esta regla aún más estrictamente con registros de confirmación . Para los comentarios de código puede suceder que arregles el código, pero olvides actualizar el comentario. Con la cantidad de código en el proyecto habitual, se garantiza que sucederá tarde o temprano. Por otro lado, el registro de confirmación está vinculado al cambio particular y se puede recuperar utilizando la funcionalidad "anotar" / "culpar" del sistema de control de versiones. De nuevo, Git y Linux tienen algunos buenos ejemplos.

Mira, por ejemplo. en este compromiso . (No copiar aquí, es demasiado largo). Tiene cuatro párrafos que ocupan casi toda la página (y un poco más de pantalla completa) que describen qué fue exactamente incorrecto y por qué fue incorrecto y que continúa y modifica todas las líneas de SES. Usan comentarios como este por dos propósitos:

  1. Todos los cambios enviados se revisan y el registro de confirmación es lo que tiene que explicar el cambio al revisor.
  2. Cuando se encuentra un error, los registros relevantes se recuperan usando "pickaxe" o "culpa" para evitar volver a un comportamiento anterior, también incorrecto.

(nota: me tomó a lo sumo 10 minutos de navegación aleatoria del repositorio git para dar estos dos ejemplos, por lo que sería fácil encontrar más allí)

    
respondido por el Jan Hudec 09.08.2013 - 09:52
29

Un comentario que le dice por qué explica el razonamiento detrás del código, por ejemplo:

// 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 comentario que le dice cómo explica lo que hace el código.

// 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 diferencia es que un mantenedor puede mirar el primero y decir: "¡Oh, así que esto puede estar fuera de fecha!" En el segundo caso, dicho mantenedor tiene un comentario que no le dice nada que el código no revela (asumiendo buenos nombres de variables).

Este es un ejemplo de la vida real de un comentario de por qué, de algún código de iOS en el que trabajé donde necesitábamos obtener una dirección de puerta de enlace (o una conjetura razonable para ello). Podría simplemente haber dejado los comentarios que decían cosas como "Inicializar el socket de recepción", pero eso solo le diría a un mantenedor (o a mí en el futuro) lo que estaba sucediendo, no por qué tuve que hacer este extraño kludge para obtener la dirección de la puerta de enlace en el primer 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
    
respondido por el thegrinner 09.08.2013 - 14:58
18

Me gustaría comenzar mi respuesta con una cita hecha por Jeff Atwood en su publicación del blog El código le dice cómo, los comentarios le dicen por qué :

  

los mejores comentarios son aquellos que no necesitas

También afirma que:

  

Primero debes esforzarte por hacer que tu código sea lo más simple posible de entender sin confiar en los comentarios como muleta. Solo en el punto donde el código no pueda ser más fácil de entender, debe comenzar a agregar comentarios.

Estoy totalmente de acuerdo y, en este punto, debo agregar que, antes de poder comenzar a hacer el código lo más simple posible, hago que el código funcione y luego comienzo a refactorizar. Por lo tanto, durante la primera ejecución antes de refactorizar, agregar por qué los comentarios ayuda mucho.

Por ejemplo, si se usan 3 bucles anidados con tablas hash bidimensionales para completar una tabla de días de la semana mientras se analizan los datos, es muy fácil perder el rastro de lo que hizo alguien o incluso de ti mismo si no se observaron durante algunas semanas y se refactorizó repentinamente .

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

El superior es un ejemplo de cómo funcionarían 3 bucles anidados antes de la refactorización.
Además, explicar algunas condiciones de la rama puede ayudar a entender el código mucho mejor con lo que se pensaba en el proceso:

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

Incluso el código simple y obvio funciona bien con los comentarios. Solo para hacer las cosas un poco más obvias, claras o fáciles de entender para los colegas e incluso para usted mismo al mantener el software.

Claro que xp dice tener un código que se explica por sí mismo, pero ¿duele un comentario de una línea?

También encuentro las siguientes reglas en este blog para ser muy útil:

  
  • Comprende el material antes de escribir
  •   
  • Escribe como si tu audiencia fuera un estudiante de cuarto grado
  •   
  • Piensa en cómo los lectores pueden malinterpretarte
  •   

Cualquier persona que tenga que volver a su propio código o el código de alguien o incluso el código heredado sabe que puede ser un dolor de cabeza. Entonces, en lugar de ser perezoso o tratar de ser un codificador de uber para no comentar nada o muy poco, ¿por qué no hacer que su propio o algún pobre insultador, que tiene que mantener su código, la vida futura sea mucho más fácil siguiendo las reglas citadas?

También se cuestionan muchas decisiones de programación hechas durante las revisiones y no siempre está claro por qué algunas partes se escribieron tal como estaban, incluso si algunas secciones del código son vitales para que un programa funcione debido a un error importante que se encontró cuando el código era utilizado durante años Así que para no aburrirlos completamente con un tl; dr cerrar con una última cita de acmqueue :

  

La documentación previa, clara y extensa es un elemento clave en la creación de software que puede sobrevivir y adaptarse. La documentación con altos estándares reducirá el tiempo de desarrollo, dará como resultado un mejor trabajo y mejorará el resultado final. Es difícil pedir más que eso de cualquier técnica.

    
respondido por el Ben McDougall 09.08.2013 - 09:45
8

Tiendo a reducir los comentarios a cualquiera de las referencias donde se explica con más detalle una determinada funcionalidad / código o para explicar por qué se elige una determinada forma de programación.

Teniendo en cuenta que otros programadores con habilidades similares usan o leen tu código, es importante comentar si usas una forma diferente a la esperada para lograr algo. Así que puedes explicar en un comentario por qué eliges de esta manera.

Por ejemplo, si puede utilizar dos sensores diferentes en un dispositivo Android y uno de ellos no se adapta a sus necesidades, puede explicar en el comentario por qué eligió el otro.

Por lo tanto, el 'por qué' debería dar un fundamento sobre sus elecciones hechas.

    
respondido por el Roalt 09.08.2013 - 09:19
8

Los comentarios deben decirle lo que no hace el código, no necesariamente delimitado por POR QUÉ , CÓMO o QUÉ . Si tiene buenos nombres y tiene funciones bien delineadas, es muy posible que el código le diga exactamente lo que está sucediendo. Por ejemplo:

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

Este código realmente no necesita comentarios. La función y los nombres de los tipos hacen que sea fácil de entender.

Sin embargo, a veces, puede ser difícil o imposible realmente hacer un código fluido como el anterior. Por ejemplo, el siguiente fragmento de código es para encontrar un punto estadísticamente aleatorio en una esfera. Las matemáticas son bastante opacas, por lo que un comentario con un enlace a la explicación es para ayudar a decir CÓMO que funciona. Esto puede incluirse en una función para decirle a QUÉ que lo hace sin necesitar un comentario si es necesario más de una vez; de lo contrario, el título del enlace también ayuda en ese 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));

Otro ejemplo de cuando los comentarios te dicen lo que no hace el código es para explicar una decisión. En el siguiente ejemplo, el código no bloquea una variable no local de subproceso dentro de un fragmento de código enhebrado. Hay una razón para esto y el comentario explica POR QUÉ . Sin el comentario, podría ser considerado un error, o simplemente no 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)
    {
        ...
    }
    ...
}

Tal vez, podría mejorarse para decir por qué el objeto aleatorio no se crea dentro del bucle paralelo en primer lugar. Si no hay razón, también podría hacer que alguien se presente y se dé cuenta de que toda la idea es estúpida y es un buen lugar para refactorizar.

    
respondido por el Chewy Gumball 09.08.2013 - 09:45
5

Puede ser útil reconocer diferentes tipos de "por qué", especialmente:

  • Las razones por las que el código que parece demasiado complejo no funcionaría si se simplificara (por ejemplo, es posible que sea necesario realizar un encasillamiento aparentemente superfluo para garantizar que el código funcione en algunos casos).

  • Las razones por las que una operación simple en particular que parece peligrosa en realidad son seguras (por ejemplo, "Nuestra rutina de obtención de datos informará que un artículo ficticio pasado el último es menor que cualquier otra cosa, y el elemento posterior es mayor" ; cualquier elemento que deba ordenarse antes de otro, en una secuencia consistente ascendente o descendente, tendrá al menos un elemento más (posiblemente ficticio) a continuación ").

En muchos casos, un comentario del segundo tipo en una parte del código puede "coincidir" con un comentario del primer tipo en otra (por ejemplo, "Si bien parece que esta secuencia de operaciones podría simplificarse, el Fitz la rutina depende de que el Wongle no sea Woozled hasta después de que el Bandersnatch haya sido golpeado. ")

    
respondido por el supercat 09.08.2013 - 18:03
2

No olvides que si estás escribiendo un programa, no solo estás escribiendo cosas al azar, lo estás haciendo porque tienes un modelo de lo que quieres , ya sea en Un documento formal o simplemente en su cabeza. Las cosas en su cabeza son tan reales como el software / los datos en una computadora (y es muy probable que contengan errores).

Es posible que alguien que lea su código no tenga ese modelo en su cabeza, por lo que los comentarios pueden servir para decirles qué modelo fue y cómo se relaciona el código con él. Creo que eso es lo que significa "por qué". Ciertamente, es bueno hacer que el código en sí sea lo más claro posible, pero eso no siempre es lo suficientemente bueno. Ejemplo:

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

Además de eso, el modelo cambia con el tiempo, y esos cambios deben transferirse al código. Por lo tanto, los comentarios no solo deben decir "por qué" hay algo en el código, sino que también es importante cómo cambiarlo en respuesta a los cambios anticipados del modelo. Ejemplo:

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

Creo que a veces se descuida el propósito de los comentarios.

    
respondido por el Mike Dunlavey 09.08.2013 - 14:11
2

No todos mis comentarios son del tipo 'por qué', pero muchos lo son.
Estos son ejemplos de un archivo fuente (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.

Tenga en cuenta que (mi) por qué los comentarios generalmente preceden al código que lo va a hacer (por lo tanto, termina con dos puntos).

Algunos tienen comentarios que explican solo lo que está sucediendo, por ejemplo. cuando un proceso tiene muchos pasos que tienen una agrupación lógica (y el código no se refactoriza para mostrarlo automáticamente), comentaré como:

// Step 1. Initialization
    
respondido por el Jan Doggen 09.08.2013 - 09:13
1

Entiendo el POR QUÉ como la razón por la que haces algo de una manera posiblemente extraña o quizás ilógica, debido a las circunstancias dadas que requieren que se haga así. El CÓMO se puede ver en el propio código, sin importar lo extraño que sea, incluso si el código no tiene "sentido". El QUÉ probablemente se explica mejor al principio de la documentación de la clase / función. Por lo tanto, le permite agregar el POR QUÉ , donde explica cualquier cosa que no esté incluida en el CÓMO y QUÉ, y las formas peculiares que debe tomar debido a razones que están fuera de su control.

Por supuesto, no siempre es así, fuera de la tierra de los unicornios y el arco iris ...

CÓMO:

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

QUÉ:

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

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

POR QUÉ:

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

Aprendí a SIEMPRE escribir comentarios en los archivos de encabezado de C ++ (ya que no siempre está claro QUÉ hace una función, aunque el nombre sea una buena sugerencia), especialmente si pasas una API a otros desarrolladores o utilizas una herramienta para autodoc doxygen.

Entonces, para mí, un comentario típico parece algo así como

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

La única vez que usé el POR QUÉ de los comentarios es algo que es difícil de entender y, a veces, incluso para el programador, como "¡NO TOQUE ESTO! Porque ..." o "EL PROGRAMA CRUCE SI LA LÍNEA ESTÁ BORRADA ..."

Las soluciones, los hacks y el comportamiento extraño califican para el criterio de POR QUÉ en mis ojos ...

Un ejemplo muy bueno e incluso divertido es esta "solución" para un código desordenado escrito por una persona llamada Richard, otra persona lo envolvió y explicó por qué en los comentarios ... enlace

Desafortunadamente, hay bastantes veces en las que se ve obligado a envolver el toro **** porque no puede tocar el original, ya sea porque "siempre ha sido así" o porque no tiene acceso o ... bueno, no tiene el tiempo para arreglar el original para el propósito realmente no califica para la sobrecarga.

    
respondido por el AnyOneElse 09.08.2013 - 13:17
0

Se supone que el código especifica el plan de ejecución. De esa manera, el seguidor del programa (o el compilador) puede averiguar qué hacer y cómo hacerlo. Lo que se divide en pasos que el seguidor del programa puede seguir. Los pasos primitivos son el cómo.

La intención del codificador es otro asunto. En código simple, claro y directo, la intención es obvia. Cualquier lector humano razonablemente competente llegará a la intención de un bloque de código, simplemente leyendo el código. La mayoría del código debería leerse así.

Ocasionalmente, la relación entre la intención y el plan es oscura. El código revela el qué y el cómo, pero no el por qué. Ahí es cuando los comentarios que revelan la intención valen la pena. La intención del programador es el por qué.

    
respondido por el Walter Mitty 09.08.2013 - 13:31
0

En este momento, este problema se está extendiendo a través de procedimientos almacenados y vistas contra un modelo de datos complejo y un tanto complicado.

Hemos creado (numerosos) selecciones como "Caso cuando x.account no es nulo y x.address en (seleccione la dirección de fedex) luego x.account else y.account end" y se espera productividad aunque existe No hay tiempo en absoluto para leer todo el código fuente. Y este tipo de ejemplo tiene sentido, pero sigue siendo inescrutable.

Los comentarios que explican por qué si en fedex entonces x y si no, entonces y - arroja luz sobre todo el sistema y cuando leemos lo suficiente, empezamos a entenderlo. Y esto se ha simplificado en exceso y hay cientos o miles de declaraciones similares. Mi corazón se ilumina cordialmente hacia quien sea el dev del tipo de 2007 quien puso los por qué.

Así que sí, los modelos complejos de datos complicados y las curvas y los procedimientos almacenados con múltiples rutas con nombres válidos, por favor, por el amor de Di-s, díganos por qué.

    
respondido por el naftalimich 09.08.2013 - 22:33
0

Acabo de escribir este comentario; es un ejemplo concreto de explicar por qué una línea de código es lo que es, y en particular por qué lo cambié.

El método examina los datos almacenados y evalúa si está completo hasta el día presente en un extremo y hasta la fecha de inicio en el otro extremo.

// 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 probablemente puedas adivinar, el operador mayor que el había sido mayor o igual. El comentario explica por qué el valor antiguo tiene sentido y por qué el nuevo valor es mejor. Si alguien ve esto en el futuro, verá que el uso de ">" No es un descuido, sino una optimización. Luego pueden cambiarlo o dejarlo, según la necesidad en ese momento.

    
respondido por el user25946 15.08.2013 - 17:56

Lea otras preguntas en las etiquetas