Comparación entre Unified Diff y Split Diff
(matklad.github.io)- En cambios pequeños, tanto unified diff como split diff permiten revisar sin problema, pero en cambios grandes y complejos, una revisión basada solo en el diff llega a su límite
- Los cambios grandes deben explorarse y validarse tomando como base el estado completo del codebase en un momento dado, como si se estuviera modificando el código real, más que siguiendo el historial de cambios
- La pantalla de revisión ideal debería mostrar lado a lado el código actual en disco y el unified diff de esa zona, para seguir al mismo tiempo el contexto del código y los cambios
- Como el soporte en las herramientas existentes es insuficiente, se usa un flujo de trabajo de baja tecnología: hacer checkout local del PR, borrar los commits y dejar solo los cambios
- Con este método se puede recorrer los archivos modificados y marcar los hunks revisados, pero todavía queda trabajo manual porque la posición actual del editor y el diff no se sincronizan automáticamente
Revisar el estado actual del código, no solo el diff
- Tanto split diff como unified diff funcionan bien en cambios pequeños y simples
- Cuando los cambios crecen en tamaño y complejidad, ya no basta con leer el diff
- En cambios grandes, hay que inspeccionar correctamente el codebase en un momento determinado
- Prestando atención a las zonas cambiadas recientemente, pero en esencia revisándolo como una revisión de código normal
- Hay que ejecutar las pruebas
- Hay que usar funciones de navegación del editor como goto definition
- Hay que aplicar cambios locales para comprobar si había otra forma posible de escribirlo
- Hay que buscar partes que debieron cambiar dentro de un contexto más amplio
- Hay que poder detectar cosas extrañas en el codebase actual, sin depender del historial de cambios
La pantalla de revisión deseada y la alternativa local
- La vista ideal del diff tendría a la izquierda el estado actual del código y a la derecha el unified diff correspondiente al área de código visible en ese momento
- El código de la izquierda debería ser igual al estado actual en disco
- Los cambios se marcarían sutilmente en el margen
- El diff de la derecha debería corresponder al área de código visible actualmente a la izquierda
- Hoy en día hay poco soporte de herramientas para este formato de revisión
- En la práctica, el flujo de trabajo consiste en hacer checkout local del PR con el script gpr
- En el último paso, se borran los commits del PR y se dejan solo los cambios
- En ese estado, se reutiliza para la revisión de código el flujo de trabajo de staging y commits
- Con edamagit se puede ver la lista de archivos modificados y moverse al cambio anterior o siguiente desde el editor
- También se puede usar el área de staging para marcar los hunks ya revisados
- La incomodidad que queda es que no existe sincronización automática entre el magit status buffer y el archivo que está abierto actualmente en el editor
- Para ver lado a lado el archivo actual y el diff, hay que abrir manualmente el diff y desplazarse por cuenta propia hasta la posición que se está viendo
- Si se quiere hacer una revisión tan cercana al código, por ahora toca construir herramientas temporales por cuenta propia
- El objetivo principal de una revisión de código no siempre es únicamente revisar el código; como lectura relacionada también se puede ver Two Kinds of Code Review
1 comentarios
Opiniones de Hacker News
Decir que, ante cambios grandes, no se quiere hacer una revisión de diff, sino revisar correctamente la base de código en un momento específico y prestar atención a las áreas modificadas recientemente, personalmente no me parece que encaje con una revisión de código común.
Dependerá del equipo y del ticket, pero si el revisor no tiene un rol tipo principal engineer, responsable principalmente de todo el código, normalmente veo la revisión de código más como una simple verificación de coherencia hecha por un segundo par de ojos.
Si los commits del PR están bien organizados o se hicieron squash, también es relativamente fácil revisar los cambios incrementales por separado.
Al final, la queja parece más una diferencia en el tipo de revisión. Hay revisiones comunes de verificación de coherencia y revisiones profundas de arquitectura/funcionalidad antes del merge, y las revisiones de código en herramientas web parecen estar, en su mayoría, más cerca de lo primero.
Una buena revisión de código empieza con un buen PR. El PR debe explicar el objetivo y cómo se logró ese objetivo.
El primer punto de una buena revisión es confirmar si el código cumple el objetivo descrito; el segundo, si hay pruebas que validen la funcionalidad declarada; el tercero, si sigue las convenciones arquitectónicas del sistema existente; el cuarto, si el estilo coincide con lo esperado; y, por último, si hay sugerencias para mejorar el uso de la API.
Una revisión de código que solo hace verificación de coherencia aporta poco, porque eso lo podría hacer CI. La revisión de código es un proceso humano, así que hay que aprovechar al máximo lo que solo los humanos pueden hacer y dejar el resto a las máquinas.
En los puntos anteriores está implícita la pregunta: “¿Este cambio será un buen ejemplo para futuras contribuciones?”.
Siempre he hecho revisiones de código desde una perspectiva de tester. Compilo y ejecuto el código para ver si funciona como se esperaba, busco puntos débiles e intento romperlo.
El diff no es el final, sino el inicio. Solo es una pista que indica qué partes del código conviene probar; al final, la prueba viene del comportamiento real.
Se podría decir “eso es trabajo de un tester, no de un ingeniero”, pero ¿quién podría probarlo mejor que la persona que entiende el código y sabe dónde mirar?
Sé que esta actitud es minoritaria. Muchos ingenieros parecen tener una resistencia extrañamente fuerte a ejecutar el código ellos mismos, incluso a usar un depurador. Construyen infraestructuras enormes para automatizarlo todo, pero la automatización, al final, no es más que código que también puede tener bugs. ¿Quién prueba las pruebas? Quis custodiet ipsos custodes?
La revisión de código es la última línea de defensa, salvo por CI, pero en los equipos donde trabajé, la gran dirección que introducía un PR ya se había discutido con varias personas antes de ese punto. Puede ser pairing, explicaciones profundas, charlas de café o cualquier método acorde con la complejidad.
Entonces el PR se convierte en un espacio para revisar las tácticas que implementan esa estrategia, no la estrategia en sí, como separar un nuevo módulo, introducir una dependencia grande o rehacer la superficie de una API.
Si se verifica la coherencia de solo una parte del código sin comunicación previa, existe el riesgo de fragmentar la propiedad del código. Se termina en una situación como: “¿Qué hace ese módulo?” “No sé, pregúntale a Bob”.
Por separado, sin importar cuál sea el modo de revisión, me gusta la idea de vista de diff que propone el autor.
Con bloques donde se mezclan líneas rojas y verdes es difícil hacer cualquier revisión. No es una vista útil del código y, al menos como vista predeterminada, no es adecuada.
Aun así, como es parte del trabajo, uno la hace a la fuerza, pero estoy 100% de acuerdo con el autor en que no es una vista muy útil.
Como no hay procesos básicos para manejar defectos, funcionalidades, etc., cada ingeniero tiene que inventar por su cuenta, una y otra vez, cómo desplegar una funcionalidad mediante colaboración.
La mayoría termina haciendo casi nada, y la revisión de código pasa a ser la única colaboración que ocurre con un nivel de detalle significativo sobre el tema. Por eso se termina intentando meter todas las intervenciones posibles en un solo momento.
Como tercera, o cuarta, opción, también vale la pena mencionar difftastic. Usa un diff “estructural” en lugar de uno por líneas, y resalta los cambios con más granularidad.
https://github.com/Wilfred/difftastic
Según recuerdo, a veces hacía que alrededor del 35% de los diffs fueran más fáciles de leer; normalmente en un 60% no había diferencia, y en raras ocasiones, un 5%, los hacía más difíciles de leer.
Lo usé hace algunos años, así que no recuerdo bien los problemas concretos. La única razón por la que dejé de usarlo fue que empecé a usar magit para los diffs de Git.
git diff --word-difftambién tiene una función similar y es bastante buena.Al revisar con Vim se siente parecido
Tengo un pequeño script que abre el PR y, para los archivos modificados, hace algo más o menos como
vimdiff <(git show baseref:file) file. Las pestañas de Vim son, en la práctica, vistas, así que es bueno poder tener buffers individuales abiertos al mismo tiempo en distintos estadosEn la vista predeterminada, el bloqueo de desplazamiento funciona como se espera, y si hace falta se puede evitar en una pestaña aparte
[cy]cse mueven entre hunks tomando como referencia el conjunto de cambios que existe en el PR, no el directorio de trabajoCon
dpydgse puede hacer push/pull de un hunk hacia/desde el buffer de diff de solo lectura para marcarlo como “terminado”, y entonces se oculta de los cambios resaltados en el buffer vivoLos cambios hechos en el buffer vivo se pueden commitear de inmediato o pushear como comentario
Las funciones normales del editor, como go to definition, integración de build y documentación en popups, también siguen funcionando tal cual sobre el estado actual del árbol
Este enfoque permite ver los cambios del PR contra la rama base y, al mismo tiempo, mantener limpio el directorio de trabajo. Me parece mucho mejor que la solución de matklad, que ve los cambios dejando sucio el directorio de trabajo
En el entorno donde trabajo esto es especialmente bueno porque los cambios a menudo se agregan como commits
--fixup, y en el momento del merge la herramienta los vuelve a combinar, llamando agit-interpret-trailerspara atribuir el autor del commit fixup al commit original. También extrae los comentarios de texto del PR y, si corresponde, agrega un trailerReviewed-by, o un trailerAcked-bysi equivale a un +1En una base de código muy grande y compleja, con muchos ingenieros involucrados, esto es un gran problema
La razón por la que el code review es difícil es que los diffs siempre parecen plausibles, las pruebas siempre pasan y lo básico siempre está revisado
Pero aunque un cambio parezca razonable, muchas veces en realidad está mal
Un solo cambio malo que parezca plausible puede hacer que toda la arquitectura derive
Como siempre, esto no es estrictamente un problema de herramientas, sino más bien de cultura y de compartir conocimiento. No se resuelve solo con mejores herramientas
Al hacer code review rara vez se busca el bug en sí. Más bien se revisa si existen pruebas capaces de detectar ese tipo de bug
Siento que me estoy perdiendo algo. El autor dice que el split diff no le sirve, pero no explica por qué
Un diff ideal se vería casi igual que un split diff sin contexto duplicado. En vez de tener el contexto duplicado en ambos lados, estaría solo a la izquierda
Me pregunto qué utilidad tiene quitar el contexto duplicado del panel derecho
Los editores/IDE que he usado normalmente tienen soporte básico para estas funciones dentro del diff. Go to definition solo funciona dentro del mismo archivo, o el autocompletado no funciona bien con elementos recién agregados, y muchas veces no hay funciones de refactorización
Según lo entiendo, el autor quiere invertir el diff. En vez de mirar el cambio en sí, quiere mirar el código y verificar si hay cambios relacionados ahí
Al artículo le faltan detalles, pero parece que quiere navegar hacia la implementación o los puntos de llamada y comprobar si hay cambios adecuados
El resaltado de diff puede distraer más de lo que uno cree, y a veces desactivarlo mejora claramente la legibilidad. Yo también lo desactivo a veces en el visor de diffs para ver el código con ojos frescos, pero no existe la opción de mostrar a la vez un diff unificado en un split pane
Si presionas
.en GitHub, entras a un IDE completo dentro del navegadorMe resultó muy útil al revisar. Como puedes ver los cambios dentro del contexto completo del archivo, en vez de solo fragmentos, detectas mucho mejor problemas sutiles de diseño
Como referencia, este atajo también funciona en GitLab
Extraño varias funciones de p4merge. Por ejemplo, tiene una función que muestra en el espacio entre los split diffs qué parte de un archivo corresponde a qué parte del otro, y al mismo tiempo muestra el impacto del diff en ambos lados: https://www.perforce.com/manuals/p4merge/Content/P4Merge/dif...
También me gusta la función de 3-way merge: https://www.perforce.com/manuals/p4merge/Content/P4Merge/dif...
Estas funciones no han llegado a las herramientas web de diff más usadas. Creo que un 3-way diff es una buena forma de mostrar el resultado de un merge difícil
Si usas GitHub, en la página de pull request de github.com puedes presionar
.o cambiar el dominio agithub.devEntonces se abre VSCode en el navegador y el pull request se muestra en una vista de diff
La ventaja es que puedes ver el archivo completo y, como usa un algoritmo de diff distinto al de github.com, a veces resulta más legible en diffs complejos
La vista de diff side-by-side que muestra los comentarios del PR como una lista fuera de las líneas podría ser muy útil, pero es una lástima tener que levantar una instancia completa de VSCode en el navegador
De hecho era tan útil que GitHub antes tenía una split view así, y no hacía falta empujar a la gente a una maraña de buzzwords como entornos de desarrollo AI/ML/crypto/blockchain/cloud-enabled
La idea es buena, pero la ejecución es pésima. También parece una buena forma de resumir GitHub en general últimamente
Meld se adapta bastante bien a este uso
https://meldmerge.org/
Mi herramienta favorita es diff2html-cli: https://diff2html.xyz/
https://www.npmjs.com/package/diff2html-cli
Permite ver diffs en HTML y admite formatos side-by-side o unified