El objetivo principal de la revisión de código es encontrar código difícil de mantener
(mathstodon.xyz)- La revisión de código se parece más a un proceso para sacar a la luz con anticipación el código que será difícil de mantener después, que a un procedimiento para detectar bugs o garantizar la integridad
- Si incluso al revisor le cuesta entender el código al leerlo, es muy probable que el mantenedor del futuro enfrente el mismo problema
- Es mejor hacer las correcciones ahora, mientras el autor original todavía recuerda el contexto, y no es realista esperar encontrar bugs de forma confiable solo con inspeccionar el código
- Más que “encuentra bugs”, para el revisor es una tarea más ejecutable “trata de entenderlo y marca las partes que no se entienden”
- Una buena revisión no empieza intentando demostrar todo a la perfección, sino dejando notas en los puntos que no se entienden y pidiendo mejoras
Cambiar el enfoque de la revisión de código
- El objetivo central de la revisión de código no es que el revisor encuentre bugs, ni garantizar que el código no los tenga
- En la práctica, es poco realista esperar que se puedan detectar bugs de forma general solo con echar un vistazo al código
- Por eso, el centro de la revisión no está tanto en “¿es código correcto?” como en “¿otra persona podrá leerlo y corregirlo después?”
El código difícil de entender es una señal de riesgo de mantenimiento
- El revisor lee el código tratando de entender qué hace y cómo funciona
- Las partes que no se entienden se convierten en una señal de riesgo de que un mantenedor futuro podría quedarse atorado
- Conviene corregir ese tipo de código de inmediato, mientras el autor original aún recuerda el contexto
Tareas de revisión más accionables que buscar bugs
- La solicitud “busca bugs en este código” es una tarea cuyo éxito es difícil de evaluar
- Aunque se encuentren algunos bugs, puede que se hayan pasado por alto otros que siguen ocultos, así que desde la perspectiva del revisor es fácil que solo quede claro el fracaso
- En cambio, la solicitud “trata de entender este código y, si no puedes entenderlo, señálalo” tiene criterios mucho más claros
- No hace falta entenderlo todo a la perfección
- Basta con registrar las partes que no se entendieron
- Si se intenta comprender el conjunto y se dejan notas en los puntos donde uno se atora, ya se cumplió el papel de la revisión
Criterios de revisión en el trabajo real
- El código que el revisor no puede entender puede convertirse, por sí mismo, en algo que debe corregirse
- Los comentarios de revisión no solo sirven para reportar bugs, sino también para poner en evidencia falta de explicación, problemas de estructura y flujos difíciles de leer
- Con este criterio, más importante que demostrar la validez del código es dejarlo en un estado que el equipo pueda leer y manejar después
Encontrar bugs es un efecto secundario
- Esto no significa que la revisión de código no encuentre bugs en absoluto
- Los bugs pueden descubrirse durante la revisión, pero es difícil esperar que sea un método para encontrar todos los bugs o la mayoría de ellos
- Una condición de éxito más realista es revisar la comprensibilidad y mejorar de inmediato, junto con el autor original, las partes difíciles de mantener
1 comentarios
Opiniones en Hacker News
La revisión de código no tiene un solo propósito. Detectar código difícil de mantener también es importante, pero no es el único propósito, y quizá ni siquiera sea el más importante.
Funciona como una salvaguarda que dificulta fusionar código malicioso, incluso si un desarrollador o una IA se van por un camino raro, y permite que alguien que no está demasiado cerca del problema vea una mejor forma de hacerlo o problemas que se pasaron por alto.
Alguien que conozca mejor otras partes del sistema también puede detectar problemas de interacción, hace que al menos una persona más se familiarice con ese código y se vuelve una oportunidad de aprendizaje tanto para el autor como para el revisor.
Es especialmente importante cuando hay diferencias de experiencia: cuando mentoreo a empleados nuevos, los agrego como revisores a todos mis PR para que vean cómo trabajo, y también reviso sus PR para orientarlos. A veces yo también aprendo de ellos.
También sirve para detectar bugs, pero no debería ser el mecanismo principal, y es especialmente importante para bugs de seguridad y rendimiento que son difíciles de atrapar con pruebas automatizadas.
En especial con la modularización y la descomposición: una vez que entiendes un PR enorme completo, se te forma un modelo en la cabeza y empiezas a ver si será mantenible, si algún día será una pesadilla total o si quedará en algún punto intermedio.
Creo que quizá la parte más importante de la revisión de código es la transferencia de conocimiento.
En nuestro equipo pequeño, salvo que sea algo urgente, todo el equipo marca aprobación en el PR antes de hacer merge, y gracias a eso todos los integrantes tienen una idea general del estado actual de la base de código. Así evitamos sorpresas como “desapareció todo un sistema del que yo dependía”, que viví antes en empresas más aisladas en silos.
También se vuelve un espacio para hacer preguntas sobre cómo funcionan las cosas y aumentar la comprensión. En un equipo que funciona bien, todos los desarrolladores deberían entender hasta cierto punto el sistema completo, incluidas las partes que no tocan directamente.
Otra función importante es la verificación del conocimiento organizacional. Hace poco cambié un poco una tabla, y un colega me avisó que eso rompería un microservicio que yo no había considerado y que escribía en esa tabla. Yo no sabía ni que ese microservicio existía ni que accedía a esa tabla, pero gracias a esa revisión pudimos evitar un problema mayor y una situación de limpieza de datos.
Al final todos están ocupados con su propio trabajo y no quieren ser la persona que bloquea un PR importante, así que simplemente aprueban, mientras que en realidad nadie está revisando el código.
Para problemas como “desapareció todo un sistema del que yo dependía”, creo que son muy importantes las pruebas automatizadas que puedan detectarlo aunque la persona que depende de ese sistema no esté presente.
También apoyo firmemente la propiedad compartida de todo. Es natural que la gente termine siendo dueña en la práctica de ciertas partes de la base de código, sobre todo de componentes que creó, pero eso lleva a silos y a un bajo factor bus. No debería formarse una estructura en la que una persona sea dueña de un sistema y ese sistema dependa de otro componente propiedad de una sola persona.
Cuando hay demasiado para leer, nadie puede seguir el ritmo; por eso se delega, se documenta y se hacen sesiones de panorama general.
Siempre he pensado que la mejor forma de ver la revisión de código es como la puerta por la que el código que pertenecía al autor pasa a ser propiedad del equipo o del proyecto.
El código que reviso no es tu código, sino código que pronto será nuestro. Naturalmente, la mantenibilidad es un factor importante dentro de eso.
Nuestro equipo empezó a usar IA, así que yo cambié a un enfoque simple. No dejo comentarios; solo tomo una decisión binaria de aprobación: “¿esto es una locura o puede pasar?”.
Estoy cuidando mi tiempo y mi salud mental.
Hacer esto solo vuelve más perezosos tanto al revisor como al autor.
El propósito de una revisión de código es multifacético. Incluye si es difícil de mantener, si puede tener bugs, si se puede hacer más simple y limpio, si se ajusta al estilo de código del proyecto, si ayuda a que otros entiendan el código, si sirve para incorporar a miembros junior del equipo, y si hace un sanity check de las decisiones de diseño.
Este tipo de textos ligeros por lo general se parecen más a una forma de justificarse para revisores de código perezosos.
Hay que ver si funcionalmente logra el objetivo según la descripción del issue o del PR, si hay código innecesario como salidas de debug restantes o claves de API privadas, y si existen defectos obvios como fugas de memoria, casos borde no manejados, fallas de seguridad o llamadas a APIs obsoletas.
También hay que mirar si puede hacerse más entendible, si conviene agregar o quitar abstracciones, si los nombres de variables y métodos son mejores, y si sería mejor usar más o menos estilo funcional.
También debe verificarse si es consistente con la base de código o la guía de estilo, si hay mejoras de rendimiento obvias como usar un hash set en vez de una lista o aplicar evaluación diferida, y si las pruebas son suficientes.
Tampoco estoy completamente de acuerdo con la afirmación de que, si el código no se puede entender, no debería aprobarse. Algún código simplemente es realmente difícil de entender, y el objetivo es que sea funcionalmente correcto y tan fácil de entender como sea posible.
Pero este texto se parece más a bait, algo como decir: “la gente cree que la cena consiste en comer comida, pero en realidad no se trata de comer, sino de conectar con familiares y amigos”. Es un tipo específico de lógica reduccionista floja que funciona bien en HN.
Sentí que la revisión y el debugging consumen mucho más tiempo que escribir o producir código, y simplemente “rezar para que funcione” nunca termina bien.
Decir que “por lo general no se pueden encontrar bugs mirando el código” no es correcto en absoluto. Se pueden encontrar bastantes en cada nivel de abstracción, y a eso se le llama code smell.
Descriptores de archivo que no se cierran, corrutinas sin await, enormes bloques try/catch que devuelven algún valor sin registrar el error, casteos de tipo incorrectos, todo eso entra ahí.
Como principio general, no hay que ver el type checker, el compilador y el runtime como simples etapas que hay que pasar. Hay que trabajar con esas etapas, reconocer que son herramientas valiosas y no trabajar en contra de ellas.
Se podría definir “por lo general” de alguna manera para que técnicamente sea verdad, pero entonces pierde casi todo significado.
Si se van a hacer afirmaciones amplias sobre la revisión de código, es importante definir de qué tipo de revisión de código se está hablando.
Hoy las revisiones asincrónicas de PR al estilo GitHub y los comentarios inline son el estándar, pero las revisiones no se limitan a eso. Antes también había procesos de revisión presencial más parecidos a la defensa de una tesis o a una presentación en una conferencia.
La literatura que muestra que la revisión de código es una práctica de calidad útil —de hecho, una de las pocas prácticas de calidad útiles— proviene en general de procesos de revisión mucho más estructurados que los actuales.
Personalmente, las revisiones de PR estilo GitHub anteriores a los LLM me parecían más bien algo para sentir que el proceso estaba bien o para marcar una casilla de gobernanza, y en la era de los LLM creo que su relación costo-beneficio empeorará mucho y terminarán desapareciendo.
Usando una analogía técnica forzada: la comunicación asincrónica por texto tiene más pérdida que el habla en cuanto a la información que puede codificar con éxito, y también tiene menor throughput. Por eso, cuando hay que intercambiar mucha información, vale la pena pagar el costo de sincronización.
Era más cercano a la ingeniería tradicional, y todos tenían que pensar en el software como algo más permanente.
Es cierto que garantizar la mantenibilidad es uno de los beneficios de la revisión de código, pero decir que ese es su único propósito es una afirmación bastante audaz.
La revisión de código también es una herramienta que permite al equipo entender los cambios de código y compartir la responsabilidad colectiva sobre toda la base de código.
La revisión de código no es una sola cosa. Hay muchas razones: compartir conocimiento, lavar responsabilidades, calidad del código, cumplimiento regulatorio, etc.; y, como siempre, el objetivo depende del contexto de uso.
Si el autor es matemático, la frase “por lo general no se pueden encontrar bugs mirando el código” probablemente no significa que encontrar bugs sea completamente imposible.
Más bien significa que no es posible encontrar todos los bugs, o encontrar un bug específico.
Conectándolo con el punto de la mantenibilidad, otro objetivo de la revisión es hacer el código lo más simple posible para aumentar la probabilidad de que sea depurable mediante revisión. No evita bugs en sentido absoluto, pero sí aumenta la probabilidad.
La primera interpretación es relevante pero falsa; la segunda es verdadera pero irrelevante.
Probablemente el autor quiso decir “un bug dado no se puede encontrar por lo general mirando el código”, es decir, “no para todo bug B es posible encontrar B”, pero eso también es verdad y está lejos del punto central.
He trabajado tanto con personas que rechazan con frecuencia las sugerencias en los PR como con personas que las aceptan.
Quien acepta sugerencias me hace pensar que, en cierta medida, está dispuesto a compartir conmigo la propiedad. Se siente como que ambos mantenemos y entendemos el código, y miramos en la misma dirección.
En cambio, con los PR de alguien que rechaza las sugerencias, disminuyen las ganas de participar. Si de todos modos lo van a rechazar, ¿para qué dedicar tiempo a revisarlo con cuidado?
thought,change,nit,fix,chat.Por ejemplo,
thoughtsería algo como “quizá más adelante foo se vuelva más común, así que refactoricemos cuando llegue ese momento”;change, “esta es una abstracción con fugas, así que me gustaría que se modelara como bar”;nit, “el nombre no es del todo intuitivo, así que consideremos Baz o Boo”;fix, “esta prueba unitaria valida el campo incorrecto”; ychat, “esta es una decisión importante que definirá la forma de las soluciones de esta categoría en adelante, así que primero hablémoslo con el equipo”.Algunos prefijos significan que el PR queda detenido hasta que se haga el cambio, y otros que el comentario se puede aceptar o no. Para quien lo escribió, queda claro qué es algo en lo que hay que “llegar a un entendimiento compartido” y qué es una “preferencia de expresión” u “observación”.
Eso sí: si dejaste un
nity la otra persona no está de acuerdo y lo ignora, no deberías molestarte. Si lo sentías con tanta fuerza, no debió haber sido unnit.