Cómo arruinar un proyecto con sobreanálisis, expansión de alcance y diff estructural
(kevinlynagh.com)- Los proyectos tienden a dividirse entre un flujo de construirlo ya y terminarlo y otro en el que la investigación y el diseño crecen hasta perder de vista el problema original; en el progreso real, muchas veces simplemente hacerlo termina adelantándose
- Un estante de cocina quedó terminado en un fin de semana, pero la exploración de workflows de diff estructural y las ideas de larga data sobre lenguajes y CAD no lograron convertirse, pese a mucha investigación y prototipos, en resultados que resolvieran directamente la motivación inicial
- Incluso al crear una búsqueda fuzzy de rutas para Emacs, las funciones adicionales de una buena librería generaron nuevos requisitos e inflaron el diseño; al final se desechó todo el código de la función de anclas, que no era necesaria, reafirmando YAGNI
- En los diff de código, la comparación por líneas no captura bien estructuras superiores como funciones o tipos, y las herramientas basadas en treesitter también pueden volverse difíciles de leer si el emparejamiento de entidades falla y muestran largas secciones como eliminaciones y adiciones
- La dirección necesaria es crear primero una herramienta de alcance mínimo pensada para la revisión por turnos de salidas de LLM; empezar con extracción de entidades para Rust y emparejamiento simple, priorizando un workflow que permita ver rápido un resumen de cambios de alto nivel
Sobreanálisis y expansión de alcance
- Los proyectos suelen dividirse entre un flujo de construirlo ya y terminarlo y otro en el que uno se hunde en casos previos, el alcance crece y al final no resuelve el problema original
- El estante de cocina hecho en un fin de semana se diseñó mientras se tomaba café, se ajustó unas cuantas veces un colgador impreso en 3D y, usando materiales sobrantes y pintura, quedó terminado dentro del mismo fin de semana
- El CAD para el colgador del bin de Ikea está publicado en OnShape CAD
- Los materiales se reutilizaron de lo que sobró del banco de trabajo, y las esquinas se redondearon a ojo con una palm sander
- En este estante, más que fabricar algo que encajara perfectamente en la cocina, el criterio principal de éxito era disfrutar la carpintería con un amigo, y eso redujo la necesidad de obsesionarse con criterios detallados
- En cambio, al buscar una herramienta de diff estructural, el resultado de difftastic dejó insatisfecho al autor, que pasó 4 horas investigando herramientas y workflows relacionados, para al final volver al criterio original: un mejor workflow de diff para usar en Emacs
- Intereses de larga data como una interfaz para prototipado de hardware, un lenguaje que mezcle Clojure y Rust, o un lenguaje para CAD consumieron cientos de horas entre investigación de fondo y pequeños prototipos, pero todavía no han desembocado en un resultado que resuelva directamente la motivación inicial
- La interfaz de prototipado de hardware aparece en septiembre de 2023, el diseño de lenguaje en noviembre de 2023, y las ideas relacionadas con CAD continúan en constraints, bidirectional editing y other dubious ideas
- En los proyectos de lenguaje y CAD, los criterios de éxito son difusos: si deben reemplazar Rust o Clojure, si solo deben abordar algunos problemas, si basta con que sean un playground de aprendizaje, si deben sustituir un CAD comercial o si también tienen que ser útiles para otras personas
- Revisar estas preguntas tiene valor, pero la idea es que conviene más construir muchas cosas reales que quedarse solo evaluando muchas posibilidades
- Aunque visto después el resultado parezca claramente malo, simplemente hacerlo tiende a dejarte más adelantado en el balance general
La ley de conservación de la expansión de alcance
- El tiempo de construir sin pensar también tiene límites y hace falta equilibrio; una experiencia de escribir mucho código con un agente LLM para luego tirarlo todo volvió a recordar YAGNI
- Se quería crear para Emacs una búsqueda fuzzy de rutas en todo el sistema de archivos al estilo Finda, y como antes ya se había implementado algo similar a mano, parecía posible terminarlo en pocas horas supervisando al LLM
- Al inicio, en una conversación de planificación se recomendó Nucleo, y como está bien diseñado y documentado, se adoptó para obtener funciones como smart case y Unicode normalization
- Por ejemplo, la consulta
foocoincide tanto conFoocomo confoo, peroFoono coincide confoo - El manejo de
cafeycaféva en la misma línea
- Por ejemplo, la consulta
- El problema no era la buena librería en sí, sino que Nucleo también soporta funciones de anclas
- En un corpus compuesto solo por rutas de archivos, el ancla de inicio de línea parecía poco útil, así que se intentó reinterpretarla como una ancla basada en segmentos de ruta
- Por ejemplo, se quería que
^foocoincidiera con/root/foobar/, pero no con/root/barfoo/
- Por ejemplo, se quería que
- Para hacerlo de forma eficiente, el índice debía almacenar los límites entre segmentos y permitir verificar rápido la consulta en cada segmento
- A eso se sumó la necesidad de manejar consultas ancladas con
/, como^foo/bar, y con solo verificación por segmentos se volvía difícil hacer coincidir correctamente rutas como/root/foo/bar/baz/ - Se invirtieron unas horas más en este diseño, intercambiando ideas con el LLM y construyendo código que envolvía tipos de Nucleo, hasta que el código se volvió demasiado grande y poco convincente, y al final se reescribió a mano un wrapper más pequeño
- Después de descansar, el autor se dio cuenta de que no recordaba haber necesitado nunca la función de anclas en Finda y que, en un corpus de rutas, poner
/al inicio o al final de la consulta podía reemplazar la mayoría de esos casos- La única excepción que quedaba era un ancla para el final del nombre de archivo
- Al final se desechó por completo todo el código relacionado con anclas, y no está claro si aun así se ganó tiempo frente a escribirlo directamente desde el principio, sin discusiones con el LLM ni con otras personas
- Parece existir una especie de ley de conservación según la cual, mientras más rápido se programa, más crecen también las funciones innecesarias, los rabbit holes y los desvíos
Diff estructural
- En código, un diff normalmente significa un resumen de cambios por línea entre dos versiones de un archivo, y en la vista unificada las adiciones y eliminaciones se marcan con
+y- - Ese mismo diff también puede renderizarse como comparación lado a lado, y mientras más complejo es el cambio, más fácil puede resultar de leer ese formato
- El problema del diff por líneas es que no reconoce estructuras superiores como funciones o tipos; si por casualidad las llaves terminan cuadrando, puede omitirse marcado incluso cuando pertenecen a funciones distintas
- difftastic intenta reducir ese problema usando el concrete syntax tree que proporciona treesitter, pero el emparejamiento de entidades entre versiones no siempre sale bien
- En el diff que disparó todo esto,
struct PendingClickno se correspondía entre ambos lados y aparecía como eliminado a la izquierda y agregado a la derecha - Sin investigar a fondo por qué falló el emparejamiento, se concluyó que, aunque el diff total fuera más largo, era preferible ver
PendingClickRequestyPendingClickcomo entidades correspondientes a ambos lados
Herramientas de diff estructural y materiales de referencia
- La herramienta de semantic diff más pulida y cuidadosamente trabajada que se menciona es semanticdiff.com
- La ofrece una pequeña empresa alemana, con plugin gratuito para VSCode y una web app que muestra diff de PR de GitHub
- Sin embargo, no ofrece una librería de código que pueda servir como base para el workflow deseado
- El texto semanticdiff vs. difftastic tiene muchos detalles útiles e incluye el problema de que difftastic ni siquiera muestra cambios de indentación significativos en Python
- En un comentario en HN, uno de los autores explica que terminó alejándose de usar treesitter para semántica, y escribe que por keywords dependientes del contexto y por el comportamiento del lexer puede fallar el parseo, al punto de que la herramienta puede detenerse si se usa un nombre como
asynccomo parámetro
- diffsitter está basado en treesitter e incluye un servidor MCP
- Tiene muchas estrellas en GitHub, pero la documentación no parecía especialmente buena y fue difícil encontrar material que explicara cómo funciona
- En la wiki de difftastic se dice que ejecuta longest-common-subsequence sobre las hojas del árbol
- gumtree es una herramienta surgida del contexto académico y de investigación de 2014
- Requiere Java, así que no encaja con el uso personal de una herramienta rápida para Emacs
- mergiraf es un merge-driver basado en treesitter y escrito en Rust
- Su architecture overview está muy bien organizada y usa internamente el algoritmo de Gumtree
- Por su documentación y diagramas, da la impresión de ser un proyecto hecho con mucho cuidado
- En un comentario en HN, el autor de semanticdiff.com dice que GumTree produce resultados rápido, pero que incluso aplicando mejoras propuestas en varios papers posteriores seguía devolviendo bastantes emparejamientos malos, por lo que terminaron cambiando a un enfoque basado en dijkstra que minimiza el costo del mapeo
- weave es otro merge-driver basado en treesitter y escrito en Rust
- La landing page vistosa, muchas estrellas en GitHub y el servidor MCP dan una impresión general algo exagerada
- Se revisó el crate de extracción de entidades sem
- El código central del diff está bien, pero es algo verboso, y el emparejamiento de entidades usa un algoritmo greedy
- Su modelo de datos no detecta movimientos dentro del archivo, aunque ese tipo de movimientos puede ser importante
- También incluye bastante análisis de impacto heurístico que parece requerir integraciones de lenguaje más sólidas para resultar confiable
- Al ejecutar
sem diff --verbose HEAD~4, incluso apareció una salida con bug en la que líneas que no habían cambiado se mostraban como cambiadas
- Al ejecutar
- Tenía demasiadas funciones hipotéticas útiles a medio terminar, como al 80%, así que no encajaba como base, aunque se valora mucho que hayan logrado tanto en tres meses
- diffast calcula la tree edit-distance de AST con base en un algoritmo de un paper académico de 2008
- Soporta Python, Java, Verilog, Fortran y C/C++ mediante parsers dedicados
- La galería de ejemplos de diferencias AST está muy bien organizada
- Puede exportar información en forma de tuplas para usarla en datalog
- autochrome es una herramienta de diff exclusiva de Clojure y usa programación dinámica
- Su explicación visual y el recorrido de ejemplos son muy buenos
- Designing a Tree Diff Algorithm Using Dynamic Programming and A* de Tristan Hume es una lectura valiosa sobre diseño de algoritmos de tree diff
El workflow deseado y el plan de alcance mínimo
- El caso de uso principal es la revisión por turnos de salidas de LLM, y no se deja que un agente genere de golpe más de 10 mil líneas de código sin control
- Al agente se le asignan tareas de alcance definido; unos minutos después se vuelve para revisar el panorama general de cambios y entonces corregir directo en Emacs, descartarlo todo e intentarlo de nuevo, o incluso reescribirlo a mano
- El workflow deseado es ver primero un resumen de alto nivel de qué tipos, funciones y métodos fueron agregados, eliminados o modificados
- Encima de eso, se quiere poder desplegar rápidamente el diff textual de cada entidad, expandiendo de forma natural el resumen hacia el diff detallado
- También se quiere poder corregir los cambios ahí mismo, sin saltar a otro lugar, es decir, edición inline sin cambiar de la pantalla de diff a la pantalla del archivo
- La meta es trasladar el workflow de revisión de cambios y staging de Magit del nivel de archivo y línea al nivel de entidad
- En línea con la lección renovada de alcance mínimo, primero se planea construir rápidamente un framework de extracción de entidades basado en treesitter solo para Rust
- El emparejamiento empezará con un enfoque greedy simple y el diff se renderizará en la línea de comandos
- Si con eso se logra un mejor resultado que difftastic en ese commit concreto, después se conectará con un workflow más interactivo en Emacs, al estilo de Magit
- Si es posible, se deja abierta la opción de reutilizar Magit mismo
- El soporte para nuevos lenguajes se agregará solo cuando haga falta
- Más adelante podría explorarse también un emparejamiento global basado en puntajes, en lugar del greedy simple
- Si el resultado es suficientemente satisfactorio, podría publicarse, pero el objetivo no es juntar estrellas en GitHub ni karma en HN; también podría quedar como una herramienta privada para uso silencioso
- La idea se cierra con la frase de que a veces uno solo quiere un estante, volviendo a enlazar la actitud de hacer solo lo necesario en vez de expandir de más
1 comentarios
Opiniones en Hacker News
Creo que esto muestra muy bien la mayor dificultad de la investigación de doctorado
Cuando eliges un tema interesante y lees tanta bibliografía previa como sea posible, es fácil darte cuenta de cuántas cosas ya existen sobre lo que querías hacer, y el scope creep tiende a volverse grave
Después de gastar toda la energía y emoción del inicio, hay que empujar a la fuerza el 20~30% restante hasta dejarlo en un estado publicable
Para el día 400, casi ya explicaste la teoría del todo y estás intentando construir un dispositivo experimental en órbita del punto de Lagrange para detectar una partícula universal que medie todas las fuerzas del universo conocido
Me pregunto cómo se puede aliviar esto
En la práctica, suele ser un trabajo como aumentar la observabilidad de un sistema del 1% al 1.001%, y se parece más a una puerta de entrada para una carrera académica
Por eso casi nunca veo tesis que sean realmente interesantes, enormemente novedosas o directamente aplicables a la ciencia
De hecho casi no he visto gente investigar así; normalmente lees dos o tres papers y construyes a partir de ahí
Creo que revisar la literatura en profundidad tiene más sentido después de obtener algunos resultados, cuando ya empiezas a redactar
No dejo de pensar en la idea de que algo mejor ya es suficiente
Incluso las mejoras pequeñas se acumulan con el tiempo, y como nada nace completamente perfecto, sentarse a intentar diseñar algo impecable desde el principio suele ser contraproducente
La frase de que el obstáculo es el camino también encaja muy bien aquí
Un colega con el que trabajaba, cuando criticaba cambios de código, si sentía que estaba señalando algo demasiado menor, decía: "es mejor que antes"
Así marcaba los puntos de mejora, pero al mismo tiempo daba permiso para seguir adelante aunque quedaran defectos pequeños, y apoyo mucho esa actitud
Antes pensaba que el perfeccionismo era solo perseguir logros demasiado altos de forma exagerada, pero también puede ser no aceptar nada si no es perfecto y abandonar sin avanzar
La procrastinación en tareas grandes muchas veces viene de la misma raíz
Me gustó algo que dijo el CEO de Rec Room
Los equipos siempre dicen que ojalá hubieran hecho el proyecto más corto; casi nunca dicen que ojalá hubieran retrasado más el lanzamiento, lo hubieran hecho más complejo o lo hubieran pulido más
No aplicará al 100% en todos los casos, pero si vas a equivocarte, me parece mejor hacer algo pequeño y lanzarlo temprano que expandirse demasiado y perder tiempo
Los humanos tienden por naturaleza a llegar a ideas parecidas, así que si completas un proyecto sin saberlo, al final es fácil que hasta cierto punto sea una reinvención
En cambio, si investigas antes, puedes desanimarte al darte cuenta de que en parte ya estás repitiendo algo que existía
Aun así, puede que lo más importante sea terminarlo por tu propio aprendizaje
Claro, es más difícil cuando necesitas producir un resultado académico nuevo o ganar dinero con un proyecto verdaderamente original, pero incluso en esos ámbitos suele haber más tolerancia de la que uno cree con solo torcer un poco lo existente
Justo ahora estoy pasando por esto en un side project
El área es Information Retrieval, así que tengo poca experiencia y obviamente hay muchísimo prior art del que aprender o que integrar
Después de leer esto, me inclino más por primero hacer lo mío y mirar casos previos solo cuando me atasque o necesite ideas
Por otro lado, viendo el documental reciente de Clojure, Rich Hickey parecía más bien haber trabajado después de pasar mucho tiempo profundizando en bibliografía previa, papers y otros lenguajes
Pero como él ya había creado otros lenguajes antes, al final el panorama general también partía de aprender construyendo directamente
Quizá la clave sea no quedarse pensando demasiado tiempo, sino construir primero, sacar lecciones en la práctica, chocar contra la pared y recién ahí ver si hace falta investigar más a fondo
Luego vi también "Easy made Simple" y "Hammock Driven Development", y me dieron ganas de aprender Clojure
Clojure documentary on CultRepo channel: https://www.youtube.com/watch?v=Y24vK_QDLFg
Simple Made Easy: https://www.youtube.com/watch?v=SxdOUGdseq4
Hammock Driven Development: https://www.youtube.com/watch?v=f84n5oFoZBc
Fijar una fecha límite me ha resuelto la mayoría de los problemas de scope creep
En mi experiencia, proyectos con deadlines duros como los game jams o concursos de programación son fáciles de terminar, mientras que los proyectos abiertos son mucho más difíciles de completar
Me parece similar a por qué el estándar de C++ también sale cada 3 años en vez de esperar a que estén listas todas las funciones deseadas
https://news.ycombinator.com/item?id=20428703
El texto me pareció interesante, pero el pensamiento del autor se veía un poco disperso
Para alguien que dice sentirse abrumado por el scope creep, parece una persona que de todos modos hace muchísimas cosas, al punto de llenar el final del texto con enlaces sobre toda clase de temas
Al final parece el tipo de persona a la que realmente le encanta aprender y probar de todo, y que disfruta mentalmente el proceso mismo de meterse en rabbit holes
Como alguien que trabaja solo, tuve una revelación que me ayudó bastante
La mayoría de las cosas que parecían abstracciones necesarias eran solo scope creep con otro nombre
Después de ir agregando flags a cada función, empecé a ver patrones en mi código y me puse una regla
No liberar una función si no tenía tests del comportamiento con el flag apagado
Eso me hizo ver los flags no como una vía de escape sino como parte del producto, y tres funciones que estaban en el backlog desaparecieron naturalmente en cuanto empecé a pensarlas así
Es cierto que el exceso de planificación y el scope creep son un problema, pero también hay que cuidarse de inclinarse demasiado hacia el desarrollo improvisado
Algunos de mis proyectos más exitosos fueron casos en los que modelé los datos y planifiqué y revisé la mayoría de las funciones antes de construir software que realmente funcionara
En esa etapa muchas veces no sabes bien qué es excesivo, y si quitas funciones que yo o los usuarios probablemente queríamos, luego terminas perdiendo mucho tiempo rediseñando a fondo el núcleo del código
En cambio, si te equivocas hacia el otro lado, el proyecto se vuelve demasiado grande y entonces lo llamas scope creep
Al final, este juicio depende de qué tan bien conozcas el dominio
Si lo conoces menos de lo que creías, hay mucho retrabajo; si lo conoces más de lo que pensabas, en realidad podrías haber ido a lo grande pero terminaste perdiendo tiempo con baby steps
Vayas por donde vayas, queda arrepentimiento, así que al final se siente como una gran cuestión de criterio
No hay que caer en la falacia del costo hundido, y que hayas investigado un tema a nivel doctorado durante unas horas no significa que tengas que usarlo sí o sí en el proyecto
Si no encaja exactamente con el problema actual, lo correcto es descartarlo sin miedo