Voy a volver a escribir código a mano
(blog.k10s.dev)- k10s era una TUI de Kubernetes con soporte para GPU creada rápidamente con vibe-coding junto a Claude, pero después de agregar la vista de fleet se rompieron varios estados de pantalla
model.gocreció hasta convertirse en un únicoModelde 1690 líneas con unUpdate()de 500 líneas, cargando con todo el estado de UI, cliente, caché, navegación y vistas- La IA agregó funciones rápido, pero también hizo crecer un god object y un key handler global, dejando una estructura donde cada vista nueva sumaba más branches al handler existente
- Los datos posicionales en
[]stringy la mutación directa desdetea.Cmden background podían provocar errores de columnas y una data race evidente - El nuevo k10s se reescribirá en Rust, y antes del primer prompt se fijarán en CLAUDE.md la interfaz, los tipos de mensaje, las reglas de ownership y el alcance
Por qué terminé reescribiendo k10s
- k10s comenzó como un dashboard de Kubernetes con soporte para GPU, una herramienta TUI pensada para que operadores de clústeres NVIDIA pudieran ver de inmediato información como uso de GPU, métricas DCGM, nodos inactivos y costos de
$32/hr - Fue escrito en Go y Bubble Tea, y se construyó durante unos 7 meses, 234 commits y cerca de 30 fines de semana en sesiones de vibe-coding con Claude
- Al principio, funciones básicas tipo clon de k9s como pods, nodes, deployments, services, command palette, live updates basados en watch y Vim keybindings ya funcionaban en apenas 3 fines de semana
- La función principal, la vista de GPU fleet, era una pantalla que mostraba la asignación de GPU por nodo, uso, métricas basadas en DCGM, temperatura, energía, memoria y estado con colores; Claude generó de una vez la estructura
FleetView, el filtrado por pestañas GPU/CPU/All y hasta el render de allocation bars - Después de agregar la vista de fleet, al volver a la vista de pods con
:rs pods, la tabla quedaba vacía, los live updates se detenían, en la vista de nodes aparecían datos stale del filtro de fleet y también se desajustaba el conteo de pestañas de fleet - Al rastrear el problema, terminé leyendo por primera vez las 1690 líneas completas de
model.gogeneradas por Claude, donde una sola estructuraModelcargaba widgets de UI, el cliente de Kubernetes, estado de logs/describe/fleet, historial de navegación, caché y manejo de mouse - El método
Update()tenía unas 500 líneas y estaba armado como una función de dispatch demsg.(type)con 110 ramas de switch/case - La IA puede crear funciones rápido, pero si se le deja seguir sin restricciones, la arquitectura se derrumba, y esa sensación de velocidad parece éxito hasta el momento en que todo colapsa al mismo tiempo
Cinco principios que salieron de los restos
-
Principio 1: la IA crea funciones, pero no arquitectura
- Claude hizo bien funciones individuales como la vista de fleet, log streaming y soporte de mouse, pero cada una fue implementada en el contexto de “hacer que funcione ahora”, sin considerar su relación con otras funciones que compartían el mismo estado
- El handler de
resourcesLoadedMsgterminó incluyendo condiciones comomsg.gvr.Resource == k8s.ResourceNodes && m.fleetView != nil, mezclando lógica específica de la vista de fleet dentro del flujo genérico de carga de recursos - Cada vez que una vista nueva necesitaba comportamiento personalizado, se agregaba otra branch al mismo handler, y además había que limpiar a mano varios fields para que los datos de la vista anterior no se filtraran a la nueva
- En
model.gohabía 9 limpiezas manuales dispersas comom.logLines = nil,m.allResources = nil,m.resources = nil; si faltaba una sola, quedaban datos fantasma de la vista anterior - La alternativa es escribir antes del código las interfaces concretas, los tipos de mensaje y las reglas de ownership, e incluirlos en
CLAUDE.mdcomo invariantes de arquitectura - Un ejemplo de regla sería que cada vista implemente el trait/interface
View, que una vista no acceda al estado de otra, que todos los datos async entren solo como variantes deAppMsgy que la structAppsolo se encargue de la navegación y el dispatch de mensajes
-
Principio 2: el god object es el resultado por defecto que produce la IA
- La IA se inclinó a una estructura donde una sola struct contiene todo, porque así puede satisfacer el prompt inmediato con la menor ceremonia posible
- El manejo de teclas tampoco estaba separado por vista, y una misma tecla
ssignificaba autoscroll en la vista de logs, shell en la vista de pods y container shell en la vista de containers - La solicitud de “agregar soporte de shell a pods” se implementó insertando otra branch cerca del key handler global ya existente
- La tecla
Entertambién se bifurcaba entre lógica de contexts view, namespaces view, logs view y drill-down genérico, todo dentro de un único dispatch plano comparando strings dem.currentGVR.Resource - Dentro del único archivo
model.go,m.currentGVR.Resource ==aparecía más de 20 veces como discriminador de tipo, y agregar una vista nueva obligaba a tocar varios handlers - La alternativa es no agregar fields de estado específicos de vista en
App/Model, crear cada vista como una struct separada y poner los key bindings en el keymap de la vista activa, dejando esa regla por escrito enCLAUDE.md - Hace falta un guardrail del tipo “agregar una vista debe equivaler a agregar un archivo; si requiere modificar una vista existente, detenerse y preguntar”, para que la IA no elija el camino más corto de sumar otra branch
-
Principio 3: la ilusión de velocidad expande el alcance
- k10s empezó como una herramienta para un público acotado que opera clústeres de entrenamiento con GPU, pero el vibe-coding hizo que funciones como pods, deployments, services, command palette, soporte de mouse, contexts y namespaces se sintieran “gratis”
- Como resultado, dejó de ser una herramienta enfocada en GPU y se fue ampliando hacia una TUI de propósito general para todos los usuarios de Kubernetes, básicamente rehaciendo k9s
- En un
keyMapplano se mezclaban bindings específicos de muchas vistas, comoFullscreen,Autoscroll,ToggleTime,WrapText,CopyLogs,ToggleLineNums,Describe,YamlView,Edit,Shell,FilterLogs,FleetTabNextyFleetTabPrev AutoscrollyShellusaban ambos la teclas, y como el dispatch revisaba el recurso actual, “funcionaba”, pero ya no era posible entender localmente el keybinding- La velocidad para escribir código parecía “shipping”, pero cada feature agregaba el costo de una branch más dentro del god object
- La alternativa es dejar claro en
CLAUDE.mdel límite de alcance: que k10s es para operadores de clústeres GPU, que las vistas soportadas se limitan a fleet, node-detail, gpu-detail y workload, y que no se agregarán vistas genéricas de recursos ni funciones duplicadas de k9s - La IA puede dar un presupuesto infinito de líneas, pero el presupuesto de complejidad sigue siendo finito, así que el alcance debe rechazarse por adelantado
-
Principio 4: los datos posicionales son una bomba de tiempo
- k10s aplanaba directamente los recursos recibidos desde la API de Kubernetes a la forma
type OrderedResourceFields []string - La función de ordenamiento de la vista de fleet trataba
ra[3]como Alloc,ra[2]como Compute yra[0]como Name, y la identidad de las columnas dependía únicamente de comentarios y del orden de columnas enresource.views.json - Si en
resource.views.jsonse agregaba una columna entre Instance y Compute, el sort, el render condicional y el drill target que referenciabanra[2]yra[3]podían empezar a fallar silenciosamente - El compilador no puede saber qué significa un
[]string, y la config JSON tampoco puede expresar comportamiento de sort, render condicional o custom drill target, así que el código Go terminaba hardcodeando supuestos posicionales - La IA tiende a elegir
[]stringoVec<String>porque son fáciles de meter directo en un widget de tabla, mientras que una struct tipada exige más ceremonia inicial y por eso queda fuera del camino rápido - La alternativa es mantener datos estructurados como
FleetNodeoPodInfohasta justo antes del render, y hacer el sort sobre fields con nombre en vez de accesos posicionales comorow[3] - Una estructura de ejemplo sería
FleetNode { name, instance_type, compute_class, alloc }, donde la identidad de las columnas queda expresada en el tipo y ya no es posible construir estados inválidos como ordenar por una columna equivocada - “Making impossible states impossible” es una expresión usada en las comunidades de Elm/Rust que significa diseñar tipos para que un estado inválido no pueda construirse, en vez de detectarlo con checks en runtime
- k10s aplanaba directamente los recursos recibidos desde la API de Kubernetes a la forma
-
Principio 5: la IA no debe ser dueña de las transiciones de estado
- La idea central del modelo de Bubble Tea es que el estado solo cambia dentro de
Update()impulsado por mensajes, pero k10s rompía esa regla - El handler de
updateTableMsgdevolvía un closure detea.Cmd, y dentro de ese closure se modificaban fields delModelcon llamadas comom.updateColumns(m.viewWidth),m.updateTableData()ym.table.SetCursor(savedCursor) - Bubble Tea ejecuta
tea.Cmden un goroutine separado, así que mientras ese closure leía y escribíam.resources,m.tableym.viewWidth, elView()del goroutine principal podía estar leyendo esos mismos fields - No había lock ni mutex, y
<-m.updateTableChansolo esperaba una señal de actualización; no evitaba queView()leyera un estado escrito a medias - Esa estructura era una data race evidente, y normalmente se manifestaba como algo que “casi siempre funciona” pero ocasionalmente rompe el display
- La alternativa es que el worker en background no mutile directamente el estado de UI, sino que envíe mensajes tipados por un channel y que el event loop principal aplique la mutación de estado al recibirlos
- La regla de concurrencia es que una tarea de background no debe cambiar directamente el estado de UI, que debe enviar el resultado como mensaje tipado, y que
render()/view()debe ser una función pura sin side effects, I/O ni operaciones sobre channels
- La idea central del modelo de Bubble Tea es que el estado solo cambia dentro de
Reglas de protección para CLAUDE.md y agents.md
-
Invariantes de arquitectura
- Cada vista debe implementar el trait/interface
Viewy no debe acceder al estado de otras vistas - Todos los datos async deben entrar como variantes de
AppMsg, y ninguna tarea en background debe mutar fields directamente - Agregar una vista nueva no debe requerir modificar vistas existentes
- La struct
Appdebe ser un router delgado encargado solo de la navegación y del dispatch de mensajes
- Cada vista debe implementar el trait/interface
-
Reglas de ownership del estado
- No se deben agregar fields de estado específicos de vista a la struct
App/Model - Cada vista debe existir como una struct separada y declarar sus propios key bindings
- La app debe despachar las teclas a la vista activa, y todo keybinding nuevo debe agregarse al keymap de esa vista, no al handler global
- Si agregar una vista exige modificar una vista existente, hay que detenerse y pedir confirmación
- No se deben agregar fields de estado específicos de vista a la struct
-
Alcance
- k10s debe ser una herramienta para operadores de clústeres GPU, no para todos los usuarios de Kubernetes
- Las vistas soportadas deben limitarse a fleet, node-detail, gpu-detail y workload
- No se deben agregar vistas genéricas de recursos como pods, deployments o services
- No se deben agregar funciones que repliquen capacidades de k9s
- Toda feature request que no ayude a operadores de trabajos de entrenamiento con GPU debe rechazarse
-
Representación de datos
- No se deben aplanar datos estructurados a
[]string,Vec<String>ni arreglos posicionales - Los datos deben mantenerse en structs tipadas hasta justo antes de la llamada a render
- La identidad de las columnas debe venir del nombre del field en la struct, no del índice del arreglo
- Las funciones de sort deben operar sobre fields tipados, no sobre accesos posicionales como
row[3] - La generación de strings para display debe ocurrir solo dentro de
render()/view()
- No se deben aplanar datos estructurados a
-
Reglas de concurrencia
- Las tareas en background como watcher, scraper o API call no deben mutar directamente el estado de UI
- Las tareas en background deben enviar sus resultados al channel como mensajes tipados
- Solo el event loop principal debe aplicar mutaciones de estado a partir de los mensajes recibidos
render()/view()debe ser una función pura sin side effects, I/O ni operaciones sobre channels- Si hace falta cambiar el estado como resultado de trabajo async, debe definirse una nueva variante de
AppMsg
Cómo lo están rehaciendo
- k10s será reescrito en Rust, no porque Rust sea mejor, sino porque se siente como un lenguaje que el autor puede dirigir directamente
- En un lenguaje que uno ha usado lo suficiente, puede detectar qué está mal incluso antes de explicarlo con palabras, y esa sensibilidad no puede ser reemplazada por el vibe-coding
- Cuando la IA produce código que parece plausible, hace falta la capacidad de detectar si en realidad es basura
- En la nueva versión, antes de escribir código, una persona hará primero a mano el trabajo de diseño, como interfaces concretas, tipos de mensaje y reglas de ownership
- En lugar de dejar que la IA tome decisiones equivocadas de arquitectura, ahora esas decisiones se fijarán por escrito antes del primer prompt
- Los enlaces al TUI original y al proyecto están en k10s Github y K10S.DEV
Nota adicional
- Bubble Tea es un framework TUI de Go basado en The Elm Architecture, y los problemas de arquitectura de k10s no vienen de Bubble Tea sino de la implementación de k10s
- “Making impossible states impossible” es una expresión de las comunidades Elm/Rust para referirse a diseñar tipos que impidan construir estados inválidos, en lugar de validarlos en runtime
- Así como en la escritura con IA el “em-dash” puede quedar como un olor, en el código con IA el “god object” puede ser esa señal, y el vibe-coding puede hacer que implementar cosas se sienta demasiado barato, llevando a pérdida de foco y bloat
1 comentarios
Comentarios en Hacker News
Quienes dicen que el código generado es aceptable, por lo general, son personas que no leen ese código
La mitigación propuesta en el artículo tampoco aguanta mucho tiempo. Al diseñar un sistema o componente surgen invariantes como “una vista no accede al estado de otra vista”, pero tarde o temprano hay que agregar una función que choca con esa condición
En ese momento, normalmente hay que abandonar la función, montarla de forma torpe e ineficiente sobre el invariante, o cambiar el propio invariante. Esa elección no es solo una cuestión de contexto, sino de criterio, y los modelos actuales fallan demasiado seguido en ese juicio
Si explicitas restricciones de arquitectura, el agente genera código complejo e inmantenible, forzado para encajar en esas restricciones incluso cuando habría que cambiarlas. Si no lo lees con más cuidado que el código escrito por personas, al final aparece “código que se devora a sí mismo” y te das cuenta demasiado tarde
La clave es identificar los puntos que a la IA le cuestan y hacérselos fáciles. Por ejemplo, se necesitan contextos extremadamente pequeños, modularización con límites claros, módulos puros separados de la entrada/salida, ocultar cosas detrás de interfaces, 100 pruebas que corran en menos de 1 segundo, benchmarks, etc.
La IA funciona bien cuando hay límites y contexto pequeño. Si no se los das, su desempeño empeora, y la responsabilidad es de quien usa la herramienta
Ninguna especificación sobrevive a la realidad, y aunque investigues y diseñes lo suficiente, al final se revela que algún invariante dentro de esa especificación estaba mal
Cuando un humano se encuentra con esto durante el desarrollo, puede dar un paso atrás y repensar si el invariante estaba mal y qué impacto tendría cambiarlo. En cambio, la IA muchas veces termina improvisando una solución hackeada bajo supuestos o diseños erróneos, y le falta la intuición para reevaluar el conjunto
Puede mejorar con buenos flujos de trabajo y validación, pero no es un área que herramientas como Claude Code resuelvan bien por defecto, y ahí hay un límite
Al principio establecimos principios fuertes y movimos a mano algunos casos de uso para ganar confianza. La migración completa era tan grande y costosa que se había postergado casi 10 años, así que intentamos acelerarla con IA para bajar el costo
La IA estuvo bien para el 80% de los casos mecánicos y simples. El 20% restante requería cambios en el framework; la mayoría eran pequeños, como agregar campos a una API, pero uno o dos requerían una reconceptualización
El backend de cierto sistema podía producir ciertos datos en el 99% de los casos, pero en algunos casos importantes lógicamente no podía y había que reportarlos desde afuera. Sin embargo, una optimización importante estaba construida sobre la suposición de que “eso es imposible”
La herramienta de IA no detectó esta situación y agregó la lógica migrada como si fuera a funcionar correctamente. Gracias a la forma en que desplegamos, todavía no era un bug en producción, pero al hacer las preguntas correctas al equipo asociado descubrimos que la misma necesidad existía en otros lugares
Al final, no se convirtió en un problema grave porque una persona estaba metida a fondo. En el futuro, herramientas de validación y modelos más inteligentes quizá faciliten más este tipo de migraciones, pero por ahora el código generado parece hermoso aunque esté roto, así que hay que vigilarlo de cerca todo el tiempo
Tenía un patrón de arquitectura raro que llevaba unos dos meses usando, y cada vez me incomodaba un poco, pero no fue hasta anoche que me di cuenta de que no era una buena abstracción y de que había una mejor forma de dividirlo
Cuando dejas que un LLM genere el código, esa incomodidad se siente mucho menos claramente, así que tardé más en detectar el problema y encontrar una solución. Está bien generar lo periférico, pero las funciones centrales todavía conviene escribirlas uno mismo en su mayoría
Incluso si los expresaras en un lenguaje formal preciso, al LLM que está debajo del agente le falta capacidad para entender por qué ese invariante es necesario y por qué importa. Puede que aparezca un LLM que conecte tokens con especificaciones formales y hasta escriba pruebas, pero seguirán saliendo códigos raros generados desde la parte informal del prompt
No se puede evitar solo agregando restricciones y prompts a una lista técnica o a una especificación. Por mejor que hagas la trampa, el animal igual se escapa
El problema es la inflación de código: se sigue agregando código para satisfacer el prompt o la tarea. Muchas veces menos código es mejor, y se necesita alguien que pueda anticipar qué quieren y qué esperan los demás. Los generadores son buenos, pero hay que usarlos con un poco más de moderación, como una manguera de bomberos
Cuando Copilot autocompletaba una sola línea, decían “igual tú tienes que escribir la función completa”; luego completó la función y dijeron “igual tú tienes que escribir la lógica alrededor”; luego completó también esa lógica y dijeron “igual tú tienes que escribir la funcionalidad”
Ahora que ya completa la funcionalidad, dicen “igual tú tienes que hacer la arquitectura”. No sé si estos modelos pueden resolver la arquitectura, pero es interesante ver cómo la expectativa se sigue moviendo
Aunque la IA complete una línea, una función entera, una funcionalidad o un ticket, igual tienes que leer y entender el código
Uso IA todo el tiempo y va mejorando, pero igual reviso cada línea. Incluso a nivel de líneas individuales, hoy no diría que sea mejor que el autocompletado con tab del año pasado; a veces es muy bueno, pero otras veces realmente es muy malo
Los LLM son excelentes para desarrollar software, pero solo cuando no les dejas escribir la arquitectura. Los módulos,
structsyenumshay que hacerlos uno mismo, y en lo posible también agregar uno mismo los campos y variantesConviene poner comentarios de documentación en cada
struct,enum, campo y módulo, y luego hacer que el LLM mire ese módulo y esas estructuras de datos para rellenar los cuerpos de funciones necesarios y cosas por el estiloAunque le digas varias veces “nunca bloquees en la ruta crítica”, el LLM igual mete bloqueos en la ruta crítica; y aunque le digas “si haces X, hace falta una prueba de tipo Y”, hace X y omite la prueba
Los humanos tampoco siguen instrucciones al 100%, pero el LLM es más aleatorio. Los errores humanos, relativamente, hacen menos veces exactamente lo opuesto de lo que uno quería
El LLM puede ver invariantes importantes en el código y aun así crear atajos, escribir pruebas que hagan pasar un fallo como éxito, decir que hizo lo pedido y enterrarlo dentro de un commit de 5 mil líneas
Estoy convencido de que los LLM son excelentes y son el futuro, y por eso estoy creando un lenguaje llamado https://GitHub.com/Cuzzo/clear para ellos. Hay que superar el problema de lenguajes que exigen contexto global donde no debería hacer falta, para que sea más fácil trabajar junto con ellos
He tenido éxitos, pero ha sido tan frustrante que a veces me pregunto si valía la pena gastar tanta cordura en esto
No significa que la arquitectura no importe, sino que una arquitectura que funcionaba bien ayer no necesariamente tiene que seguir siendo la correcta hoy
Al usar agentes de programación, me puse algunas reglas
Primero: si voy a generar código con un agente, tiene que ser algo de lo que esté absolutamente seguro de que yo mismo podría implementar bien si tuviera tiempo
Segundo: si no es así, no avanzo hasta entender completamente lo generado y poder reproducirlo por mi cuenta
Tercero: si rompo la segunda regla, puedo generar deuda cognitiva, pero tengo que pagarla por completo antes de declarar terminado el proyecto
Cuanto más se acumula esa deuda, más probable es que baje la calidad del código generado después, y da la sensación de crecer con interés compuesto. En proyectos personales este enfoque es agradable, aprendo mucho y me queda un codebase que entiendo con comodidad
Hace falta encontrar un punto de equilibrio donde sigas conectado al codebase sin convertirte en el cuello de botella del equipo
Claude es un matemático nivel doctorado y yo no, pero yo sí sabía exactamente qué propiedades quería de la solución y cómo probar si era correcta. Así que dejé la solución de Claude en vez de mi solución simple e ingenua, lo anoté en el pull request y todos estuvieron de acuerdo en que había sido la decisión correcta
Me pregunto si en casos así debería haber una excepción. Si la IA llega a ser mucho mejor que yo no solo en matemáticas avanzadas sino también programando, la pregunta más interesante es si dejaría por completo de escribir código a mano, aunque eso implique perder mi capacidad de juzgar el código directamente, bajo la premisa de que sí podría seguir evaluando las pruebas
Es una expresión más precisa, porque la deuda que se acumula es exactamente falta de comprensión del código
No sé por qué de repente con la IA tendría que tratarse distinto
Al final hay que juzgarlo por riesgo y recompensa. Hay que evaluar cuál es la pérdida si sale mal, qué tan probable es detectarlo en pruebas y revisiones, y qué beneficio obtienes si sale bien. Lo mismo aplica a librerías y servicios externos
Un contrato complejo de criptomonedas con reglas financieras imposibles de actualizar y sin pruebas no tiene para nada el mismo riesgo que un visor para visualizar datos internos de logs
En teoría suena bien, pero en la práctica siempre terminas tomando atajos mentales sin darte cuenta
Si comparo arreglar un problema en un codebase desconocido por mi cuenta versus sentir que “entendí por completo” lo que hizo un agente, una semana después no me queda lo mismo en la cabeza. Si lo hago yo mismo, se acumula como conocimiento general y normalmente permanecen las partes importantes; pero si intento apropiarme como mío de lo que hizo el agente, en el momento parece que lo entendí y luego se me olvida muy rápido
Por eso concluí que en estos casos la ayuda del LLM suele ir contra mis objetivos, incluso sin considerar otras preocupaciones como el tiempo o la presión del negocio
A mí me pasó exactamente lo mismo
La estafa funciona así: en un buen codebase, la IA puede crear muchas funciones y hasta parece más rápida, más segura y más precisa. Sobre todo en áreas que uno no conoce bien, esa impresión es aún más fuerte
Con el tiempo el codebase crece, lleva más tiempo navegarlo y la tasa de fallos sube. Como cuesta admitirlo, uno insiste más, hasta que se detiene recién cuando cambiar algo ya es casi imposible
Cuando vuelves a mirar el código, “spaghetti” se queda corto: es más bien una Gran Muralla China
Al final borré 75 mil líneas de 140 mil, y siento que esos tres meses de inmersión fuerte en agentic coding fueron una pérdida. Construí funciones inútiles, aumenté los bugs, perdí mi modelo mental del código, me salté decisiones difíciles que solo se ven cuando estás dentro del código, y también les fallé a los usuarios
No lo digo con sarcasmo; de verdad me da curiosidad cuál era la expectativa inicial y de dónde salió
Parece que con los LLM la gente tiene expectativas diferentes. Si le pasaras a un “desarrollador” cualquiera que solo conoces por internet una descripción resumida de una funcionalidad y te devolviera un montón de implementación medio rota, nadie se sorprendería
Sin embargo, a veces la gente espera de una máquina que alucina largamente milagros que ni siquiera esperaría de un humano. Me pregunto de dónde sale esa confianza
Igual que una gran ciudad es una colección de ciudades pequeñas, debe haber un mapa, y uno debería poder hacer zoom a un área local y trabajar dentro de ese alcance. No hace falta conocer todos los detalles de Nueva York para ir a tomar un café
Crear una arquitectura sana y mantenible es responsabilidad de quien usa la herramienta. La IA no lo impide, y si usas bien la herramienta incluso puede ayudar
Por ejemplo, tratar de inmediato el código generado por IA como código legado, poner fronteras de encapsulación fuertes e interfaces bien definidas, y luego integrarlo en un flujo más manual
Hay todo un espectro, desde prompts aislados hasta generación de código en línea, y la forma adecuada cambia según el problema y la parte del codebase
La generación aislada encaja más en la etapa de prototipo, cuando repites mucho la especificación; cuando el prototipo se asienta, puedes bajar a generación por módulo o por archivo para trabajar de forma más sistemática, manteniendo en ese nivel un buen modelo mental
Si sí lo leyeron pero no lo entendieron, entonces podrían haber pedido comentarios detallados en cada salida; y si saben que al modelo le cuesta más a medida que crece el codebase, entonces cuanto más aumenta la complejidad, con más rigor hay que revisar la salida
Crear islas de código de mayor calidad, usar la IA para ayudar a reconstruir la intención del desarrollador y las reglas del negocio, y construir seams y pruebas unitarias alrededor del módulo objetivo
La IA no necesariamente tiene que servir para aumentar throughput; también puede ser una herramienta flexible de exploración y refactorización que ayude al trabajo manual o a implementaciones posteriores con agentes
Cada vez que veo textos como este termino comparando la velocidad que la gente dice obtener con IA con la velocidad que yo obtengo simplemente programando a mano
Casualmente llevo 7 meses trabajando en un proyecto de MMO 3D, y ya se puede jugar, a la gente le parece divertido, los gráficos están bien y puedo meter fácilmente cientos de personas en el servidor. La arquitectura también está bastante bien, así que es fácil extender funciones, y probablemente podría lanzarlo tras cerca de un año de desarrollo
Mientras tanto, el autor original no pudo ni hacer una TUI básica en 7 meses de vibe coding. La velocidad de funcionalidades puede sentirse alta, pero para crear una UI básica como esa parece increíblemente lento. Hay muchas buenas librerías de TUI y es el tipo de cosa que se puede hacer a mano en unas semanas llenando una tabla con los datos necesarios
Con IA la sensación de avanzar rápido y mucho es fuerte, pero en la práctica muchas veces parece bastante más lenta que programar manualmente. Los datos de productividad también parecen respaldar la idea de que los usuarios de IA sienten que van más rápido, pero en realidad producen menos
El mayor consumo de tiempo en el desarrollo de software son las reuniones para alinear expectativas de stakeholders y soluciones. Desde esa perspectiva, la IA casi no ayuda, así que si comparas horas-persona desde la propuesta hasta entrar al loop de pruebas, probablemente el resultado sea decepcionante
Pero para resolver problemas, corregir bugs e implementar soluciones ya aprobadas, siento que es por lo menos 10 veces mejor que antes. No solo en tiempo puro, sino también en la capacidad para interpretar comportamiento observado e investigar problemas
Eso sí, hay gente que no puede obtener resultados precisos y valiosos con IA. Si sabes exactamente qué quieres y cómo lo quieres, la IA ayuda muchísimo. Si le pides algo que yo igual iba a hacer, lo hace más rápido. Pero si no sabes con precisión lo que quieres, la IA perjudica el avance
Cuando la gente muestra cosas hechas con LLM, rara vez impresionan, porque en su mayoría son cosas que también se pueden hacer a mano en muy poco tiempo
Tampoco he observado un aumento de software realmente impresionante, lo que parece coincidir con el hecho de que hoy los LLM se usan para resolver problemas simples más que problemas importantes
Además, algo de lo que no se habla mucho es la calidad del código
Un codebase hecho con vibe coding es un gran ejemplo de que los LLM no son tan buenos escribiendo código. Corrigen sus propios errores y enseguida los vuelven a introducir, y tampoco usan patrones con consistencia
Últimamente Claude a veces toma decisiones de estilo de código “interesantes” que no encajan con el estilo actual del codebase
Hay que frenar esa repetición con lenguaje tipo “desarrollador senior”
La parte de “antes de escribir código, diseño yo mismo interfaces concretas, tipos de mensajes y reglas de ownership” es precisamente la parte difícil de programar
Si ya tienes la arquitectura, escribir el código es muy fácil. Si no escribes el código tú mismo, es más difícil darte cuenta de que diseñaste una API que permite
nullpero la base de datos no, o que aunque lo permita, igual dejaste pasar otros problemas pequeñosNo entiendo cómo incluso después de escribir este texto no se dio cuenta de que el problema era la IA. No solo porque le dejó la arquitectura a la IA, sino porque no miró con atención todo lo que la IA iba haciendo
La IA es un generador de código embellecido, y hay que verificar todo lo que hace. La parte difícil de la ingeniería de software nunca fue escribir código, sino todo lo demás
Los desarrolladores que creen que programar es la parte difícil aman de verdad el AI coding, porque algo que antes era difícil ahora se volvió fácil
En cambio, para quienes creen que escribir código es fácil, programar es un problema de abstracción, mantenibilidad y escalabilidad. Lo difícil es sentar una base sensata para que el software pueda crecer; si encuentras la abstracción correcta, lo demás es relativamente sencillo
Para este tipo de personas, el AI coding es una herramienta útil, pero no una herramienta mágica. El autor original notó los límites de la IA, así que pertenece al segundo grupo y vio la parte difícil que la IA no puede hacer
De un lado están quienes usan un autocompletado potente o un chatbot al lado, pero revisan claramente todo; del otro, casos como el de Steve Yegge promocionando un nuevo editor para coordinar decenas de agentes como si no fuera a leer la mayor parte del código: https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16d...
El primer grupo sigue pensando a fondo el diseño, las interfaces y las estructuras de datos, y revisa con fuerza. El segundo grupo no, y por eso preocupa más
Sigo un enfoque plan → red/green/refactor, y el plan en sí suele verse bastante plausible y bien fundamentado porque absorbe toda la documentación y discusiones de foros
El problema es que cuando empieza el trabajo, inevitablemente aparece algún punto donde la documentación y la implementación real no coinciden. Puede ser que esa combinación de herramientas nunca se haya usado así, que la documentación esté desactualizada o simplemente que haya un bug
Aun así, si el objetivo del proyecto o de la funcionalidad está lo bastante claro y se puede ejecutar y probar localmente, el agente puede quedarse iterando en un callejón sin salida arquitectónico y aun así salir de ahí. Incluso mira dependencias y código de librerías y propone correcciones upstream, algo bastante parecido a lo que yo haría en una sesión profunda de debugging
Por eso estoy bastante conforme con el modo de dar instrucciones y supervisar en vez de hacer yo mismo las tareas aburridas. Pero una buena parte de mi equipo no profundiza tanto en problemas de arquitectura y su respuesta por defecto es “escalarlo al arquitecto”, así que no creo que eso sea bueno a largo plazo
La ventana en la que uno puede ejecutar y entender todo parece cerrarse rápido. Aun así, del mismo modo que usamos compiladores sin entender por completo cómo convierten a código máquina o cómo funcionan totalmente la predicción de saltos y la caché de CPUs modernas, quizá nos adaptemos creando nuevas herramientas y frameworks
Desde la perspectiva de alguien sin tanta experiencia programando, estoy aprendiendo más que nunca al verificar los resultados y ver qué está bien y qué está mal
Por eso tampoco parece que vaya a mejorar radicalmente pronto. Cuando la gente me pregunta “¿cómo haces para que la salida de Claude sea tan buena?”, la respuesta siempre es “la miré con cuidado, encontré problemas y le pedí a Claude que los corrigiera”. De verdad eso es todo, pero apenas lo oyen ya se les apaga la mirada
Es como Google: facilitó muchísimo encontrar información, pero no eliminó el factor humano necesario para distinguir la buena de la mala
Primero pienso el problema y diseño la estructura y la API; solo entonces le encargo a la IA la implementación
El título dice “volví a escribir código a mano”, pero en realidad lo que hace es “hacer a mano el trabajo de diseño antes de que se escriba el código”
Entonces parece que el código todavía lo genera Claude
Más grave aún: cuesta entender que durante 7 meses pensara que su proyecto de vibe coding funcionaba bien sin ni siquiera mirar el código fuente generado, y que además hasta compró el dominio
Si es un side project y vas verificando gradualmente siguiendo los diffs, no es tan raro no mirar el código a fondo. Sin duda es otra forma de trabajar, pero tampoco es una locura total
Se siente como ver a desarrolladores hacer un speedrun de las lecciones de gestión de proyectos y gestión de producto
Ahora están viendo que las especificaciones sí son útiles y que escribir mucho código incorrecto no hace que un proyecto vaya más rápido. Los desarrolladores se molestan porque reuniones y discusiones les interrumpen la escritura de código, pero muchas veces esos procesos existen precisamente para evitar que todos escriban más de algo equivocado
También se dieron cuenta de que la gestión del trabajo es útil, y ahora que se habla cada vez más de diseñarlo todo por adelantado, van camino al waterfall
Lo siguiente será ponerle nombre al prototipado, hablar de funcionalidades incrementales que gestionan requisitos viejos y nuevos al mismo tiempo, y al final dirán que hace falta más involucramiento del cliente
Hace falta ver qué hacen realmente los project managers y product managers. Ellos conducen un producto que es código, pero no se espera que lean el código, y tienen que lograrlo solo con lenguaje natural
¿Creen que los humanos no escriben cosas rotas? ¿Que no pasa que un equipo se va por el camino equivocado y quema una semana o incluso meses? Ahora con vibe coding puedes vivir todo eso en 30 minutos. Como ex technical product manager, se siente exactamente igual
En realidad no parece que esté escribiendo código a mano, así que la diferencia entre el título y la conclusión confunde