- Recientemente, los desarrolladores están invirtiendo más tiempo en corregir y complementar código generado por LLM (modelos de lenguaje de gran escala)
- Igual que con el código legado existente, para modificar el código de forma segura primero hay que entender qué se implementó y por qué se hizo así, pero el código de LLM dificulta aún más este proceso
- Algunos equipos revisan y rehacen el código con suficiente profundidad, por lo que avanzan más lento, pero muchos terminan integrando en el repositorio código apenas probado y ni siquiera legible
- Esto genera “deuda de comprensión (Comprehension Debt)” y, cuando al final hay que modificar ese código, regresa como un costo de tiempo mucho mayor
- En general, los LLM pueden servir para resolver problemas hasta alrededor de un 70%, pero no pueden evitar el “Doom Loop” de fallas repetidas, y al final inevitablemente llega el momento en que una persona debe entender y corregir el código directamente
El problema del código generado por LLM y la deuda de comprensión
- En los entornos de desarrollo recientes, viene aumentando el uso de herramientas de generación automática de código basadas en LLM como ChatGPT y Copilot
- Estas herramientas generan rápidamente código complejo incluso sin que el desarrollador tenga conocimiento o comprensión directa del mismo
- Pero en ese proceso, al no quedar claro la intención, las limitaciones y el funcionamiento del código, se acumula la llamada deuda de comprensión
¿Qué es la deuda de comprensión (Comprehension Debt)?
- La deuda de comprensión se refiere al estado en que los miembros del equipo no entienden suficientemente la calidad, la estructura y la intención del código
- A corto plazo puede aumentar la velocidad de desarrollo, pero a largo plazo existe el riesgo de derivar en varios efectos secundarios, como mayores costos de mantenimiento, aparición de bugs y limitaciones para ampliar funcionalidades
Riesgos adicionales del código generado por LLM
- El código generado por LLM produce resultados con rapidez sin ofrecer comentarios claros ni contexto
- Es muy probable que queden expuestos problemas de poca compartición de conocimiento entre el equipo y de falta de compatibilidad con los sistemas existentes
- Si se depende repetidamente del código de LLM, puede terminar provocando una caída en la confiabilidad del código de todo el proyecto
Comparación con la deuda técnica
- La deuda técnica tradicional es el resultado de concesiones conscientes por parte de los desarrolladores, mientras que la deuda de comprensión basada en LLM puede acumularse de forma inconsciente, lo que la vuelve más riesgosa
- Es difícil reconocer el problema, y rastrear su causa y resolverlo se vuelve más complejo
- Al intentar resolverlo, es común experimentar el “Doom Loop” (un círculo vicioso en el que el LLM sigue intentándolo repetidamente y aun así falla)
Conclusión e implicaciones
- Al final, una persona tiene que leer y corregir el código directamente
- Al usar LLM, es importante hacer el esfuerzo de documentar y compartir para que todo el equipo entienda el contexto y la intención del código
- Se necesitan mecanismos a nivel organizacional como reforzar el code review, mejorar los comentarios y la documentación, y realizar sesiones de compartición de conocimiento
- Sin gestionar la deuda de comprensión, las ventajas de las herramientas de automatización pueden convertirse, por el contrario, en un riesgo a largo plazo
- Toda la industria está sentada sobre una montaña de deuda de comprensión que crece rápidamente
8 comentarios
Yo también, como la IA no siempre puede decidir por completo, tomo todas las decisiones en el código y verifico por mi cuenta si quedó bien.
Para mí, es simplemente una herramienta para escribir snippets de menos de 10 líneas (p. ej., parseo de JSON, implementación de ordenamiento). Incluso usándolo solo así, siento que ahorra una enorme cantidad de tiempo.
Está bien usar la IA para armar la estructura base, pero parece que pulir los detalles hasta el final es realmente muy difícil.
Yo también, personalmente, he sentido a menudo los efectos secundarios cuando escribí con un LLM del 0 al 90, así que últimamente, en lo posible, hago yo mismo del 0 al 90 y lo uso principalmente en el proceso de pasar del 90 al 100, y estoy satisfecho con eso.
Opiniones de Hacker News
He visto que, a medida que crece la dependencia de los LLM, problemas que ya existían se terminan agravando aún más.
Presenta el concepto de "building theory" de Naur y señala que, cuando un equipo de desarrollo se desarma, ese programa en la práctica queda muerto.
Menciona la idea de Lamport de que "programming ≠ coding" y enfatiza que la esencia de programar es construir una teoría sobre qué se quiere lograr y cómo hacerlo.
Creo que, mientras menos acostumbre un programador a construir los modelos o teorías necesarias, más tiende a sentir que el LLM lo ayuda rápidamente.
De hecho, he tenido la experiencia de que uno puede crear más valor en un proyecto de software cuando se aleja por completo de la computadora y de la tecnología por un rato.
Cuando uno sabe exactamente qué quiere, la velocidad de desarrollo se dispara, y ahí el LLM se vuelve muy útil.
Si el objetivo está claro, también se puede distinguir de inmediato cuándo el LLM está alucinando.
No creo que sea efectiva la idea de simplemente sentarse frente a un lienzo en blanco y empezar.
El LLM puede ayudarte a arrancar, pero si no tienes cuidado muchas veces te termina desviando hacia cualquier lado.
Los problemas más difíciles, de hecho, los he resuelto mientras pensaba cocinando en la cocina.
Una de las razones por las que me ha ido algo mejor que a mis colegas usando AI para programar es que, incluso antes de adoptar LLM, ya tenía el hábito de prototipar algo rápido y luego rehacer por completo la estructura o descartarla, una y otra vez.
Este enfoque —prototipado rápido, refactorización continua, etc.— es bastante conocido, pero muchos ingenieros tienden a querer diseñarlo todo perfecto desde el principio o, al contrario, seguir parchando un prototipo sin mejorarlo de verdad.
Con AI también se vuelve barato probar varias implementaciones en paralelo, así que puedes experimentar y comparar distintas versiones, formando más rápido y de manera más efectiva una teoría o diseño del programa más sólido.
La AI acorta ese loop y permite concentrarse más en revisar los resultados, haciendo el proceso de desarrollo mucho más eficiente.
Me impresionó cómo el concepto de "building theory" se conecta con la "deuda de entendimiento" del código generado por LLM.
Creo que esto no solo tiene que ver con la programación, sino también profundamente con la forma en que pensamos y entendemos.
El código, texto o imágenes que genera un LLM no son más que resultados superficiales, y si uno no construye o experimenta directamente la teoría que hay detrás, inevitablemente se queda en un entendimiento superficial.
Incluso si algún día los LLM llegan a encargarse también de esa construcción de teoría, siento que un entendimiento artificial en el que el ser humano no interviene directamente tendría poco significado.
Me preocupa que, mientras más conveniente sea que una máquina piense por nosotros, más se vaya atrofiando poco a poco la capacidad humana de <entender>.
Estoy de acuerdo con Lamport, pero al mismo tiempo creo que la AI también puede ayudar, al menos en cierta medida, en ese proceso de "building theory" —es decir, en el entendimiento del codebase, los algoritmos y del sistema como un todo— tanto para el equipo actual como para uno que reciba el proyecto después.
Aunque no pueda reemplazar por completo todo el conocimiento, espero que la AI sí pueda cerrar parte de esa brecha.
Me tocó una vez recibir un proyecto con un equipo nuevo después de que desaparecieran todos los desarrolladores originales, y fue durísimo porque todo el conocimiento del equipo anterior se había evaporado.
Tratamos de entender el diseño original, pero terminamos generando bugs por todos lados, y al final hubo que reescribir y ampliar gran parte del código.
Aun así, dentro de esas dificultades, terminamos recorriendo por cuenta propia muchos de los mismos problemas de diseño.
Los LLM suelen producir soluciones que funcionan, pero muchas veces el código que generan es muchísimo más complejo de lo necesario.
En el momento en que se escribe por primera vez, uno entiende mejor que nadie cuál es el problema y puede eliminar esa complejidad con facilidad; pero más adelante es mucho más difícil simplificarlo en serio, porque ese código complejo empieza a parecer indispensable.
Quien mantiene el código suele no tener suficiente contexto ni conocimiento de fondo, así que muchas veces ni siquiera percibe que existía una alternativa más simple.
Primero, hay que redactar prompts claros para orientar al LLM y evitar que produzca resultados innecesariamente complejos.
Segundo, siempre hay que transmitir reglas, entrenamiento o contexto que lo lleven a resolver el problema de la forma más simple posible.
Por último, si la solución generada resulta compleja, basta con pedirle en un prompt adicional que reduzca la complejidad.
El problema de que los LLM generen enormes cantidades de código difícil de depurar sí existe de verdad.
Está la regla de que "los equipos que valoran la calidad deben revisar y entender bien el código", pero la generación es tan rápida que la revisión muchas veces no da abasto, se vuelve un cuello de botella o termina reducida a una aprobación formal.
A mi alrededor siguen agregando más procesos de revisión, pero ese enfoque sirve para desarrolladores junior; la AI no aprende, así que los mismos problemas se repiten.
Los LLM necesitan muchísimo contexto, pero la mayoría de la gente los usa con casi nada de información, tipo "hazme una función que resuelva este problema".
Al final, todos estamos acumulando montañas de código que nadie entiende, es decir, deuda técnica.
Lo que hace falta de fondo son mejores "primitivas" para transmitirle al LLM, con más eficacia, por qué debe hacer esto, con qué contexto, con qué intención y reflejando qué antecedentes.
Si el code review se vuelve el cuello de botella, aun así me parece mejor que el cuello de botella sea solo la revisión y no tener que hacer tanto la codificación como la revisión a la vez.
Además, ya existen herramientas para complementar este proceso, como lint, fuzzing y tests.
Para quienes no tienen habilidad para arquitecturar un proyecto completo o leer y analizar código rápido, la era del LLM puede ser difícil, pero esas capacidades sí se pueden desarrollar y, con el tiempo, todos se van a adaptar.
Me gusta este campo y veo estos desafíos nuevos con una actitud positiva.
He tenido la experiencia de crear distintos archivos de instrucciones
.mdpara mantener actualizadas las reglas de código que los agentes deben seguir, las trampas que deben evitar sí o sí y enlaces a documentos con estándares de codificación.Modelos como Gemini y Claude reflejan razonablemente bien este tipo de instrucciones basadas en documentos, aunque a veces repiten errores (e.g., aunque les indiques que no usen
autoen C++, igual a veces lo hacen).Espero que, a medida que mejoren los modelos, también mejore este manejo del feedback.
Al final, sentí que el punto ideal para salir del "vibe coding" es que la persona se ocupe directamente de la estructura del código y de cómo interactúan las unidades, mientras la AI se concentra en la sintaxis y el tecleo; ese compromiso mejora la productividad y permite que el desarrollador siga llevando la dirección general.
Después de cambiar mi forma de pensar sobre cómo armar prompts y contexto, mi intuición para usar LLM mejoró bastante.
Empecé a abordarlo desde la idea de reducir, mediante prompts y contexto, el espacio de probabilidad de los resultados posibles.
Ese proceso también es muy efectivo para inyectar de forma reutilizable la teoría del código.
Eso sí, preparar ese contexto requiere mucho esfuerzo y reflexión, y todavía no es fácil encontrar la línea entre lo que conviene implementar uno mismo y lo que conviene delegar al LLM.
Coincido con la idea de que "las primitivas son distintas", y creo que la unidad realmente importante es el "test".
Hago que el modelo escriba también los tests cuando genera código, y siempre reviso si esos tests son fáciles de leer.
El código solo debería mergearse si pasa toda la suite de tests.
También hay que mejorar continuamente la infraestructura de testing para que la suite completa no se vuelva demasiado lenta.
El código legacy de la vieja escuela se caracterizaba por no tener tests, y en la era del LLM pasa exactamente lo mismo.
Sobre eso de que "se vuelve un cuello de botella", cuando copio y pego código de StackOverflow igual siempre lo leo y lo reviso antes de usarlo, así que de todos modos la persona ya era el cuello de botella.
Al final, sin pasar por ese proceso, es prácticamente imposible usar el código de manera segura.
Hace poco, en el pódcast de Dwarkesh Patel, un invitado recomendó la novela <A Deepness In The Sky>, y al leerla me llamó mucho la atención cómo cobran importancia sistemas computacionales de miles de años de antigüedad y el conocimiento legacy del pasado.
Es una gran novela que trata de forma entretenida el valor del código legacy y del conocimiento organizacional, así que vale la pena recomendarla.
Pódcast: https://www.youtube.com/watch?v=3BBNG0TlVwM
Información del libro: https://amzn.to/42Fki8n
Es una pena que haya fallecido el autor Vernor Vinge; siento que sus ideas se vuelven cada vez más inspiradoras y realistas con el paso del tiempo.
La parte donde describe el proceso de convertirse en programador en un futuro lejano me pareció realmente fascinante.
Debido a la enorme cantidad de librerías y paquetes, la habilidad principal ya no era escribir código nuevo, sino encontrar módulos existentes y combinarlos.
Se retrata que la verdadera destreza está en entender a fondo los matices y la interpretación del código ya existente.
Parece que "A Deepness In The Sky" es el segundo libro de una serie; me pregunto si está bien leerlo sin haber leído antes el primero.
Pregunto si se puede leer directamente.
La mayoría de los desarrolladores no entiende el ensamblador ni el lenguaje máquina hasta ese nivel.
Los lenguajes de alto nivel se convirtieron en la capa central para la comunicación y la colaboración entre personas.
Con la llegada de los LLM, esa capa está siendo desplazada por el desarrollo basado en lenguaje natural y especificaciones.
Al final, creo que los lenguajes de alto nivel, tras décadas de evolución, ya transmiten la especificación del comportamiento de un programa en una forma casi óptima.
Si intentamos abstraer más usando lenguaje natural, hay pérdida de información; si bajamos a lenguajes de más bajo nivel, todo se vuelve demasiado verboso.
El salto hacia el lenguaje natural no es simplemente un cambio de capa de abstracción, sino algo fundamentalmente distinto.
Las herramientas anteriores —ensambladores, compiladores, frameworks, etc.— se basaban en lógica codificada de forma rígida y se podían verificar matemáticamente, pero con los LLM estamos entrando en un mundo mezclado con incertidumbre, conjeturas e incluso alucinaciones.
Muchísimos desarrolladores de JavaScript ni siquiera entienden a fondo conceptos de alto nivel como estructuras de datos adecuadas, el DOM o la API de Node.
Se genera una dependencia excesiva de la capa de abstracción y se llega a un punto en que ya no se entiende bien cómo funciona por dentro.
Desde fuera, es inevitable preguntarse: "¿qué está pasando realmente aquí?"
Este fenómeno ya se acepta como algo cotidiano.
En el fondo, como nadie entiende con precisión el interior del código, se explica de forma metafórica que tampoco habría una diferencia esencial aunque lo escriba un LLM.
Sí creo que las especificaciones en lenguaje natural tienen un rol, pero también hace falta una capa intermedia con semántica estricta.
Por ejemplo, en la combinación de Rust con LLM, un sistema de tipos fuerte cierra muchos estados inválidos y, aunque compilar tome más tiempo, el resultado final suele acercarse bastante a lo que uno quería.
Existe esa cultura en la comunidad de Rust de que "si compila, por lo general funciona"; claro, todavía puede haber bugs lógicos, pero el espacio real de errores se reduce.
Idealmente, me gustaría una pila donde haya un lenguaje de programación estricto que defina con precisión el significado lógico, junto con precondiciones, postcondiciones y demás especificaciones, y donde el LLM convierta el lenguaje natural en una especificación formal.
El lenguaje natural, por definición, no es claro del todo: incorpora ambigüedad.
En los lenguajes de programación, una ambigüedad al parsear se ve como un bug grave; en el lenguaje natural, en cambio, esa ambigüedad cumple funciones comunicativas como la poesía, el matiz o la insinuación.
La naturaleza determinista (
import) de los lenguajes de alto nivel no es la única diferencia.En programación, el determinismo no lo es todo, y un código cuyo significado no es claro para una persona puede seguir siendo perfectamente determinista.
Es decir, el eje de "abstracción alta/baja" y el eje de "determinismo/probabilismo" son problemas completamente distintos.
Para mí y mi equipo, esto parece una enorme ola de trabajo que se nos viene encima.
Llevamos casi 8 años rescatando código legacy y sosteniendo así el negocio, pero últimamente la demanda ha bajado porque las empresas están dependiendo del LLM para programar.
Aun así, si aguantamos unos 18 meses más, predigo que aparecerá una oportunidad gigante cuando empiecen a llover solicitudes para arreglar toda la deuda técnica basada en LLM que se está acumulando en poco tiempo.
Pronto se va a notar el daño de toda esa deuda que Claude dejó acumulada diciendo "ahora este código ya está a nivel de producción".
Escuché una anécdota de un conocido que estaba revisando un PR generado por un LLM.
A simple vista era un PR donde la funcionalidad parecía funcionar perfectamente, pero al mirar adentro descubrió que en realidad era una trampa: no actualizaba el backend, solo manipulaba la caché.
Le costó mucho convencer a su manager de que ese código no se podía mergear.
Al final, esto me hace sospechar que hay bastante software "vibe coded" en el mundo que solo parece funcionar en la superficie.
En esos casos, creo que hasta puede ser mejor dejar que lo mergeen y sufran directamente las consecuencias después.
La gente suele aprender recién cuando vive en carne propia el resultado de una mala decisión.
Sin feedback no hay aprendizaje, y un manager así lo único que hace es elevar expectativas y presión poco realistas sobre el equipo de desarrollo, hasta quemar a la gente y provocar rotación, en un círculo vicioso.
Me pregunto cómo un engineering manager no técnico puede haber sobrevivido tanto tiempo en esta industria.
En los últimos 15 años casi no he visto managers realmente no técnicos.
Si un manager así está causando problemas, creo que habría que escalarlo a un CTO, CEO, owner o inversionistas.
No solo pasa con el código generado por LLM: cualquier código escrito por otra persona te hace pagar una "deuda de entendimiento".
Incluso en empresas grandes como Google, para que un ingeniero nuevo haga cambios grandes en un algoritmo hacen falta meses de entendimiento previo.
Aun así, el código bien diseñado por personas es claramente más fácil de entender que lo que suele producir un LLM.
Puedes pedirle una explicación directa a una persona, y aunque un LLM también te dé respuestas plausibles, existe una diferencia real en el contexto.
Yo también he hecho bastante "vibe coding", pero al final, como no se construye un modelo mental del código, uno ahorra tiempo al principio y luego pierde mucho más cuando toca depurar.
Tampoco es realista pretender definir todo el diseño perfectamente desde el inicio.
Y no me parece confiable usar las "líneas de código aprobadas" como criterio para justificar productividad o ahorro de tiempo.
Usar código apoyado por LLM sí funciona cuando se revisa con cuidado y se va moldeando hasta dejarlo en la forma correcta.
Ese proceso se parece más bien al "pair programming".
Se enfatiza que usar salidas del LLM directamente y sin ninguna revisión ("vibe coding") no puede ser realmente eficiente.
Citando la ley de Kernighan, dicen que "debuggear es dos veces más difícil que escribir código".
Por eso, si un LLM genera el código, uno podría pensar que haría falta un LLM dos veces más inteligente para depurarlo.
Mi experiencia no ha sido necesariamente esa; al final, yo siempre tengo que seguir en el asiento del conductor.
Uso sobre todo Claude Code, y cada vez que comete errores tengo que señalárselos con claridad para que los corrija, o marcarle específicamente la zona problemática.
Como el LLM no sabe por sí mismo qué error cometió, se siente un poco como trabajar con un desarrollador junior.
O sea, depurar sí puede hacerse al mismo nivel de inteligencia, pero el LLM no tiene conciencia del problema por sí solo.
Sobre la idea de que "para debuggear hace falta alguien dos veces más inteligente", creo que también podría relacionarse con las diferencias entre los distintos modos de "profundidad de pensamiento" de los LLM.
Si se trata de una función compleja, uso el método de pedirle primero que escriba y adjunte comentarios de código explicando con claridad la intención y el enfoque en su informe del LLM.
Hay un comentario que dice que, incluso cuando se reutiliza algo escrito por otro desarrollador, también hace falta revisión, así que no sería distinto del caso de un LLM, pero yo no lo veo así. En el caso de los humanos, funcionan cosas como la reputación, el prestigio, las recompensas y los castigos. Probablemente, si alguien trabajara ahora como lo hace un LLM, lo despedirían de inmediato.
Siempre pienso: "La IA no se hace responsable por ti."
Creo que no está lejos el día en que se prohíba usar IA al programar. Puede parecer una locura, pero yo sí creo que se va a volver una realidad.