3 puntos por GN⁺ 2024-09-12 | 1 comentarios | Compartir por WhatsApp
  • Los comentarios pueden usar lenguaje humano, que es más expresivo que los identificadores, así que son adecuados para dejar constancia de opciones que no aparecen en el código y alternativas descartadas
  • “comment the why, not the what” es un enfoque que busca meter la mayor cantidad posible de información en los identificadores, pero últimamente también hay una tendencia a trasladar incluso las razones a nombres largos de funciones o de pruebas
  • En el build epub de Logic for Programmers se usó una implementación lenta que reemplaza en secuencia 16 símbolos matemáticos en cada cadena, pero como por ahora solo hay 25 cadenas matemáticas, sigue siendo lo suficientemente rápida
  • Este tipo de comentario después indica dónde corregir cuando las cadenas matemáticas crezcan a cientos y el build se vuelva un cuello de botella, y deja claro que el código lento fue un trade-off consciente
  • Los nombres de funciones o las pruebas se adhieren a lo que el código realmente hace, así que les cuesta autodocumentar información negativa sobre alternativas no elegidas y cosas que no hace

Información que un comentario transmite más fácilmente que el código

  • El código es un lenguaje de máquina estructurado, y los comentarios se escriben en lenguaje humano más expresivo
  • “comment the why, not the what” puede interpretarse como que hay que poner la mayor cantidad posible de información en los identificadores
  • Los identificadores se parecen a una forma limitada de lenguaje humano dentro del código; no pueden contener todo el “what”, pero en muchos casos sí bastante
  • Últimamente ha crecido la postura de que incluso el “why” puede ir no en comentarios sino en nombres largos de funciones o de casos de prueba
  • Las bases de código autodocumentadas por lo general aumentan la documentación mediante más identificadores
    • Como excepción, hay casos donde se dice que convertir comentarios en logging vuelve el código más autodocumentado

Qué abordan los comentarios de “Why Not”

  • La información que cuesta expresar con código es la información negativa
  • La información negativa llama la atención sobre lo que no existe en el sistema y por qué no se eligió cierta alternativa
  • “why not” no busca explicar lo que el código hace, sino dejar registro de las opciones que no tomó y las razones

Caso de reemplazo de símbolos matemáticos en epub

  • En el build epub de Logic for Programmers, por razones técnicas, notaciones matemáticas como \\forall no se convertían a símbolos como
  • Para resolverlo, se escribió un script que reemplaza directamente los tokens dentro de cadenas matemáticas por sus símbolos Unicode correspondientes
  • La implementación más fácil consiste en llamar a string = string.replace(old, new) para cada uno de los 16 símbolos matemáticos necesarios
  • Ese enfoque es ineficiente porque recorre cada cadena 16 veces, aunque la alternativa de hacer los 16 reemplazos en una sola pasada es más compleja
  • La idea central del comentario que se dejó es la siguiente
    • Recorre cada cadena 16 veces
    • El libro actualmente solo tiene 25 cadenas matemáticas y la mayoría tiene menos de 5 caracteres
    • Por eso sigue siendo suficientemente rápido
  • Este comentario explica no solo “por qué usar código lento”, sino también “por qué no usar código rápido”

Una señal para quien lea después

  • Aunque el código lento no cause un problema ahora mismo, puede volverse problemático más adelante
  • Si en una futura versión de Logic for Programmers las cadenas matemáticas aumentan a cientos, esa etapa del build podría convertirse en un cuello de botella del build completo
  • Si se deja esta señal ahora, más adelante se puede ver de inmediato qué parte hay que corregir
  • Aunque el código siga funcionando sin problemas, el comentario preserva el hecho de que el autor sabía del trade-off
  • Cuando dentro de 2 años se vuelva a abrir epub_math_fixer.py, hará menos falta investigar si ese código lento se debió a falta de experiencia, falta de tiempo o un error
  • Un comentario negativo deja constancia de que se conocía la implementación lenta, se evaluaron alternativas y se decidió no optimizar

Por qué es difícil reemplazarlo con nombres de funciones y pruebas

  • Un nombre de función como RunFewerTimesSlowerAndSimplerAlgorithmAfterConsideringTradeOffs es demasiado largo y aun así no explica bien el trade-off real
  • Si después se optimiza el código, puede haber que cambiar ese nombre en varios lugares
  • Un problema aún mayor es que ese tipo de nombre no dice qué hace realmente la función, así que termina debilitando la autodocumentación en vez de fortalecerla
  • Los identificadores, como nombres de funciones y variables, solo pueden contener una cláusula de información
  • Es difícil meter al mismo tiempo en un solo identificador “lo que hace la función” y “el trade-off que acepta la función”

Información negativa que también cuesta dejar en pruebas

  • Se podría crear una prueba que haga grep de los bloques matemáticos del libro y falle si la cantidad supera 80
  • Pero una prueba así no evalúa directamente a EpubMathFixer
  • Dentro de la función no hay un punto donde esa prueba pueda engancharse
  • La autodocumentación se adhiere al código escrito y explica lo que ese código hace
  • Como la información negativa trata sobre lo que el código no hace, no encaja de raíz con la autodocumentación

Una pregunta más amplia

  • Los comentarios de “why not” pueden verse como un caso de razonamiento contrafactual
  • Queda abierta la pregunta de si los elementos abstractos de la comunicación humana en general pueden autodocumentarse
  • Sigue en duda si información como metáforas, incertidumbre o afirmaciones éticas también puede autodocumentarse

1 comentarios

 
GN⁺ 2024-09-12
Comentarios de Hacker News
  • Me acordé de un chiste que creo haber visto en Twitter hace unos años: “los ingenieros junior escriben comentarios que explican qué hace el código, los ingenieros intermedios escriben comentarios que explican por qué lo hace así, y los ingenieros senior escriben comentarios que explican por qué el código no fue escrito de otra manera”

    • Es totalmente cierto. Incluso hubo veces en que tuve que poner un comentario 5 veces más largo que el código para evitar que alguien, con buena intención, lo refactorizara sin conocer el código, el dominio o las precauciones al tratar con un rango dinámico grande
      Por otro lado, si el significado quedaba claro solo con el nombre de la función y de las variables, también he escrito funciones bastante grandes sin comentarios
    • Hago las tres cosas. Los comentarios de resumen son súper útiles y están validados incluso por estudios empíricos, pero mucha gente está demasiado metida en la forma de pensar tipo Clean Code como para que tenga remedio
    • Los programadores junior normalmente o no documentan nada o documentan absolutamente todo. Con la experiencia aprendes que solo hace falta documentar lo inusual, y con más experiencia todavía, cada vez hay menos cosas inusuales, así que también disminuyen los comentarios
      Por eso gana el lado con menos comentarios, pero cuando ves una base de código sin comentarios, solo revisándola directamente puedes distinguir si es una obra maestra pulida con elegancia o código inestable hecho por principiantes
    • También hay comentarios que explican por qué hay que seguir reproduciendo un comportamiento que claramente se ve tonto. Es porque alguna otra cosa depende de ese comportamiento tonto
    • Si eres un ingeniero de nivel C, dejas comentarios como: “Se desperdiciaron X horas refactorizando este código. Si decides seguir los pasos de tus predecesores, incrementa este contador”
      Aunque parezca un parche total, a veces de verdad es lo mejor que se puede conseguir
  • Si creo que me va a servir cuando vuelva a ver el código dentro de un año, lo dejo todo en comentarios. Normalmente es el por qué y el por qué no, y cuando el código es complejo, también agrego brevemente el “qué” para ver mejor el flujo
    Lo que no sirve son los comentarios obligatorios. Una API pública sí debe estar bien documentada, pero algunas organizaciones fuerzan comentarios incluso en funciones privadas, y termina siendo solo repetir con otras palabras un propósito demasiado obvio por el propio nombre de la función. Eso no solo es una pérdida de tiempo, también te vuelve insensible a los comentarios y te enseña a ignorarlos
    Tampoco me gustan los comentarios de relleno que agregan las herramientas. Lo de poner //for o //try en cada loop es especialmente malo

    • Curiosamente, muchos esquemas de colores de resaltado de sintaxis apagan visualmente los comentarios y reducen el contraste. Probablemente porque los comentarios obligatorios o generados suelen tener poco contenido informativo
      Mejor eliminar los comentarios obligatorios y generados, y en temas oscuros cambiar los comentarios a un neón brillante para que resalten. Si un comentario está ahí, debería significar que es importante
    • Antes yo era del bando de “todos los comentarios son code smell”, y en gran parte todavía lo soy, pero trabajar en una base de código muy grande desarrollada y mantenida activamente por cientos de personas suavizó un poco mi postura
      Para mantener sistemas grandes y antiguos en un entorno de negocio con plazos ajustados, a veces hay que hacer cosas raras, y en esos casos hay que explicar por qué
      Una razón importante para oponerse a los comentarios es que los comentarios también pasan a formar parte del código y necesitan mantenimiento. Pero la mayoría solo los lee cuando se atasca, y además los editores suelen difuminarlos en gris, haciéndolos psicológicamente invisibles. Por eso los comentarios envejecen muy fácil
      Me pregunto si ese contexto para el “yo del futuro” también es útil en una base de código compartida que toca mucha gente, o si es un estilo que funciona mejor cuando solo unos pocos tocan el código
    • Totalmente de acuerdo. Cuando hay demasiados comentarios, se vuelve difícil ver qué hay dentro de una clase o función. Si una clase o función que originalmente cabía en una pantalla deja de caber por culpa de los comentarios, aparece un costo de legibilidad
    • Ayer, en un proyecto personal, vi una línea con un comentario que decía: “¿Esto de verdad es útil?”, y parecía que se podía quitar fácilmente. Intenté usar una clase nueva y más limpia, pero al final realmente hacía falta ese método viejo y específico
      Así que le agregué “=> yes!” al comentario existente, y agradecí que mi yo del pasado hubiera documentado esa duda. En el trabajo, sobre todo al corregir bugs, suelo dejar una o dos líneas de comentario junto con el número del ticket encima de cambios que no son obvios
  • Mi formato favorito de comentario en código es esta plantilla: “DEAR MAINTAINER: este código está así por [razón]. Si intentas ‘arreglarlo’ y descubres que fue un error terrible, por favor incrementa el contador total_hours_wasted_here = n como advertencia para la siguiente persona”
    Yo no fui el autor original, pero lo usé con agradecimiento una o dos veces, y me daba risa ver commits de una sola línea que solo aumentaban el contador

    • Ojalá hubiera tenido esto hace unos años. Una vez hice código de generación de SQL que tenía que usar recursión con bastante libertad para cumplir los requisitos, y aunque había mucha recursión mutua y el código se veía desordenado, era un mal necesario
      Un ingeniero más senior recibió la base de código, “arregló” todo para que fuera iterativo, e incluso quiso sermonearme por email sobre por qué la recursión era mala, pero su código en realidad no cumplía todos los requisitos y al final terminó reconstruyendo, en esencia, lo que yo había hecho con recursión
      Después se disculpó por parte de lo que dijo, pero si hubiera habido un comentario así al principio, tal vez se habría evitado todo ese trabajo
  • Estoy de acuerdo en que el título es ambiguo, y por eso terminé leyendo el artículo. Personalmente prefiero usar pocos comentarios en general, pero los comentarios explicativos del texto claramente tienen valor. Es un buen recordatorio de que hay que explicar por qué se hizo así y por qué no de otra forma
    Esto aplica especialmente a tu propio código si va a seguir manteniéndose dentro de 5, 10 o 15 años. Hace poco, revisando código nuevo de un colega, pensé “¿por qué hizo esto así?”, y 10 líneas más arriba estaba la razón por la que yo mismo había hecho exactamente lo mismo 8 años antes. Ese colega simplemente estaba siguiendo una regla clave del mantenimiento: hacer que se vea como el código existente

    • Cuando mantienes una base de código antigua, hacer que se vea como el código existente está tremendamente subestimado. Por la salud mental de quienes vengan después, por favor hagan que se vea como el código existente
  • Es solo un caso especial de un principio más amplio: poner comentarios en las cosas sorprendentes al leer el código
    Al escribir código, si en tu cabeza te preguntas constantemente “¿podré entender este código después?” y cada vez respondes por instinto “sí”, eso es arrogante y muchas veces está mal. Si la respuesta es “no estoy seguro”, la siguiente pregunta naturalmente es “¿por qué?”, y esa respuesta es justo lo que debería ir en el comentario
    A veces la respuesta es “porque quien lo lea podría preguntarse por qué no se escribió de otra manera”, y ese es el caso especial del que trata este texto. Pero a veces es “porque no está claro cómo funciona o por qué es correcto”, y entonces hace falta otro tipo de comentario

    • Si primero intentaste escribir el código de una forma y no funcionó, así que necesitaste un segundo enfoque, eso es una señal fuerte de que hace falta un comentario ahí. Si te sorprendió mientras lo escribías, es muy probable que dentro de 1 año, al leer el código después de haber olvidado esa sorpresa, te vuelva a sorprender
    • Con una idea parecida, uso como criterio “si esto no funciona como se espera, ¿dónde tendría que mirar?”. Si la respuesta no está en la documentación, o si está a más de 10 líneas de distancia dentro de la ruta wiki → paquete/módulo → archivo → clase → función/método, entonces agrego un comentario inline o actualizo la documentación
      Esto pasa sobre todo cuando se recortan cadenas o se recorre una estructura de datos poco común en un paso intermedio
  • Solo con los identificadores se puede avanzar bastante, pero no hasta el final. En lo personal, me gusta exigir comentarios de documentación como jsdoc/xmldoc para métodos públicos o variables, campos y parámetros
    Ponerle un buen nombre a un método también es importante, pero escribir brevemente qué hace lo vuelve más claro y en especial hace visibles defectos evidentes. Muchas veces, en el momento de escribir la primera oración se te ocurre un nombre mejor, y si en la explicación empieza a aparecer “y”, eso es señal de que el método está haciendo demasiadas cosas y se puede dividir de forma más lógica
    Es fácil pensar que una propiedad es tan obvia que no necesita documentación, pero algo como /** The API key */ string ApiKey; deja demasiadas cosas fuera. No se sabe de dónde viene esa clave, si es solo para uso interno o si se intercambia con sistemas externos, si es obligatoria, si puede ser null o vacía, si tiene longitud máxima, qué pasa si el valor es inválido, o si hay más código o documentación para leer
    Es información que a quien la escribió originalmente le toma 1 o 2 minutos agregar, pero a alguien nuevo podría costarle horas averiguarla cuando le toque modificarla, usarla o entrar a corregir un bug años después

  • Si en una revisión de código puedo anticipar lo que dirá una persona revisora demasiado puntillosa, suelo poner comentarios del estilo “no hice X por Y”. La idea es reducir discusiones molestas de ida y vuelta

    • Por experiencia, la cantidad de ida y vuelta sigue igual, pero al menos unas cuantas veces puedes responder “como dice el comentario”
    • Suelo agregar ese tipo de cosas de forma preventiva en el PR, pero no estoy muy seguro de que valga la pena dejarlas dentro del código
  • Hay otra variante de comentarios como “recorremos cada cadena 16 veces, pero hasta ahora el libro solo tiene 25 cadenas de fórmulas y la mayoría tiene menos de 5 caracteres, así que es suficientemente rápido”
    Consiste en poner logs de depuración que se activen cuando la entrada crezca mucho más que las restricciones originales del diseño. Le dan casi el mismo mensaje a quien desarrolle en el futuro, pero permiten detectarlo antes y reducir todavía más el tiempo de diagnóstico y depuración

    • En muchos casos es una buena idea. A veces uno usa un loop que por ahora funciona, pero es muy lento, y se podría poner un temporizador y hacer algo como “si tarda más de X segundos, deja un log de advertencia”
      En un sistema ideal de logging y observabilidad, se mediría el tiempo de todos los componentes de la app y se dejaría información de depuración con frecuencia, pero quién usa realmente un sistema tan perfecto. Es más importante el esfuerzo de agregar logs concretos en partes que podrían volverse lentas después o que no hubo tiempo de optimizar
      Viéndolo en retrospectiva, parece una idea obvia, pero hasta ahora lo que hacía era dejar comentarios en los lugares que había que volver a revisar después, esperando acordarme de esos comentarios cuando las cosas se pusieran mal
    • Herramientas bien intencionadas como bazel muchas veces tratan la salida que no es error como ruido que hay que ocultar. Suelen mandar estos mensajes al basurero más cercano, así que en algunas áreas los logs se vuelven un medio muy poco confiable para transmitir restricciones
  • Sin importar lo que digan, uso muchos comentarios y docstrings por todo el código. Pero lo hago al revés: primero escribo de forma aproximada, como comentarios, la lista de etapas de la aplicación; luego, mientras desarrollo, divido las etapas grandes en etapas pequeñas, a veces borro los comentarios originales y a veces los dejo, y voy refinando los comentarios hasta que casi se convierten en el algoritmo terminado
    Como normalmente programo de afuera hacia adentro, también escribo código mientras voy dividiendo los comentarios. A veces programo todo de una sola vez y después agrego comentarios hasta un punto que a la mayoría le daría flojera. Pongo explicaciones a todas las funciones y variables, e incluso a la función deg_to_rad le agrego """Converts degrees to radians.""". El almacenamiento es barato
    Sé que a la mayoría no le gusta, pero no pasa nada. Si no les gusta verlo, pueden quitarlo con un script o eliminarlo en code review. Aun así, me resulta mucho más agradable leer mi código viejo que el código ajeno sin comentarios. En Python, el código simple y repetitivo como una Flask API suele ser autoexplicativo, pero justo ese tipo de boilerplate a veces cambia mucho y termina llevando comentarios importantes. En la industria, la parte algorítmica se reescribe completa con más frecuencia
    Seguiré prefiriendo los comentarios y los docstrings

    • Yo hago algo parecido, pero sobre todo solo en los componentes de más alto nivel. Ahí es donde los comentarios son más útiles
      Me gusta hacer la conceptualización completa en mi cabeza, y al inicio siento que experimentar escribiendo varias opciones de diseño reales me vuelve más lento. Por eso, una vez que una decisión de diseño queda definida, hay que documentarla sí o sí. Los demás todavía no pueden entrar a mi cabeza
      Espero que los detalles se vuelvan más claros para otros una vez que también terminen de conceptualizarlo. Aun así, si por alguna razón esa conceptualización no ocurre, puede hacerse más difícil de leer, así que además lo pulo para mantener al menos una legibilidad básica
    • Los comentarios son excelentes cuando se mantienen bien. Pero salvo en casos como el open source, donde hay muchísima más gente leyendo que manteniendo los comentarios, al final todos se olvidan o les da flojera seguir manteniéndolos
      Muchas veces actualizar comentarios termina siendo tanto trabajo como corregir el código. Por eso, en la práctica, los comentarios normalmente son mentiras esperando ocurrir. Al final, los comentarios y el código dejan de coincidir, y eso puede ser peor que no tener comentarios
      Mejor pruebas automatizadas que muestren la intención. Las pruebas por lo general no pueden mentir, porque si no, no se habrían fusionado. Si hay un buen conjunto de pruebas que muestre cómo se pretendía usar el código, eso es descriptivo y además casi garantiza ser verdad, así que eso sí quiero verlo
    • Iba bien hasta la parte de “si no les gusta, pueden quitarlo con un script en su propia versión o borrarlo en code review”; a partir de ahí, como compañero de equipo, me parece una gran señal de alerta
    • Yo también soy parecido. Más o menos la mitad del tiempo empiezo escribiendo comentarios que expliquen lo que quiero hacer, y luego agrego qué no salió como esperaba y qué tuve que hacer para lograr que de verdad funcionara
      Ayuda muchísimo cuando hay que volver a verlo 5 semanas después
    • No creo que siempre hagan falta docstrings, ni que haya que documentar todos los parámetros y valores de retorno de todas las funciones. Pero en una API compleja sí son claramente útiles
  • Sigo la idea de que “los comentarios son una disculpa para mi yo del futuro”
    Si el código es raro o lento, o si al explicárselo a alguien me sale decir “está medio improvisado”, normalmente dejo un comentario. En especial, si ya intenté cambiarlo antes, documento los casos en que no funcionó o lo que terminé corrigiendo
    Si lo abordas con ese criterio, los comentarios innecesarios desaparecen de forma natural, y normalmente solo terminas documentando el por qué cuando de verdad hace falta. Si lo pruebas en tu propio codebase durante más o menos un mes, vas a entender la sensación