Pensando en lo que vengo experimentando en el día a día, trabajo en un producto en producción cuya base de código es gigante y está mal arquitectada. No tenemos una especificación técnica de calidad que cubra lo que ya existe, y mucho menos las nuevas funcionalidades. No tenemos un code style, esa cosa que normalmente sirve para organizar cómo los humanos van a leer el código unos de otros. No tenemos una suite de tests integrados que cubra todos los casos, incluidos los que fallan y generan bugs funcionales en producción. Ese es el suelo desde el que parto, porque creo que aquí la experiencia concreta importa más que la teoría.

Usar IA para implementar una spec completa me llevó a entender algo que no esperaba: mantener un humano en el loop vuelve el trabajo puramente lento, principalmente por culpa del review. Y la razón se vuelve clara cuando lo miras de cerca. Para aprobar lo que el modelo generó, el humano necesita entender el código, entender el style, y sentirse cómodo con las decisiones de arquitectura e implementación que el modelo tomó. Así que acaba perdiendo tiempo criticando el trabajo del modelo según su propio criterio, y ese criterio casi siempre es subjetivo y nunca fue especificado, porque se ve que falta especificación hasta sobre cómo la arquitectura debería funcionar. Es decir, nos trabamos midiendo la salida del modelo contra una regla que vive solo en la cabeza de una persona. No es la generación lo caro, es esa evaluación contra un estándar fantasma.

La recomendación

La recomendación, entonces, sería abordar los cambios con tests bien especificados, dejar de hacer code review con la mirada de que un humano va a mantener el código después, y producir documentación de calidad sobre el estado actual de la arquitectura, sobre los requisitos funcionales y no funcionales, además del estilo de código. Creo que invertir tiempo exclusivamente en ese aspecto puede acelerar mucho la adopción y el desarrollo con modelo.

Pero necesito ser honesto conmigo mismo, si no esto se vuelve entusiasmo vacío. La frase “no revisar como si un humano fuera a mantener el código” es la parte más arriesgada de lo que estoy diciendo, y no quiero que se lea como “nadie más necesita entender nada”. La forma sobria es otra: la revisión no desaparece, sube de capa. En vez de revisar cada línea generada, pasas a revisar la spec y los tests, que son la verdadera fuente de la verdad, y el código se vuelve un artefacto de build. Es el mismo movimiento que ya hicimos una vez, cuando dejamos de leer assembly y pasamos a confiar en el compilador. La confianza migró del resultado a la herramienta y a la fuente de nivel más alto. Lo que estoy proponiendo es repetir ese salto una capa más arriba. Alguien todavía va a depurar producción de madrugada, alguien todavía responde por el sistema, solo que el esfuerzo humano deja de diluirse línea a línea y se concentra donde el retorno es mayor.

La objeción que el equipo va a levantar

Sé cuál va a ser la resistencia más visceral del equipo, porque es legítima como sensación: no se puede revisar todo el código que el modelo genera. Y mi respuesta a eso es una pregunta. ¿Ya haces eso hoy con tus dependencias?

Me gusta usar el ejemplo de Rust. Un proyecto que usa Tokio carga decenas de miles de líneas de un runtime asíncrono corriendo en el corazón del sistema, líneas que nadie en el equipo leyó y probablemente nunca va a leer, y aun así confiamos, subimos a producción y dormimos de noche. Sube un nivel, a la infraestructura. ¿Revisaste el código de Kafka antes de publicar el primer evento en él? ¿Leíste el fuente de RabbitMQ, de Postgres, de TimescaleDB antes de confiarles tus datos? Claro que no. Confías porque eso funciona según las especificaciones funcionales y no funcionales que tienes para cada uno, es decir, comportamiento documentado, garantías, un contrato, más un historial largo y tus propios tests en la frontera.

Entonces el criterio nunca fue “leí cada línea”, siempre fue “tengo un contrato en el que confío y una forma de verificar el comportamiento en el borde”. Leer línea por línea era solo un sustituto que usábamos para nuestro propio código, por no tener nada mejor a mano. El código que el modelo genera es apenas más código en la misma categoría, código que no escribiste. Entonces la reacción honesta no es exigir lo imposible, es darle a ese código lo mismo que le das a Postgres, un contrato claro (la spec) y verificación en la frontera (los tests). Vale una salvedad para no ser perezoso con la analogía: Tokio y Postgres recibieron golpes de millones de usuarios durante años, y el módulo que el modelo acaba de generar tiene historial cero. Entonces la confianza no puede venir aún de la reputación, tiene que venir entera del contrato y de la verificación, lo que sube la vara de los tests y la spec en vez de bajarla. Que es, convenientemente, exactamente lo que vengo defendiendo.

El lenguaje se vuelve un detalle

Hay una consecuencia de todo esto que me parece la parte más interesante. Si la especificación es la fuente de la verdad y el código es un artefacto de build, entonces el lenguaje de programación deja de ser una decisión tallada en piedra y se vuelve un target de renderización. Con una spec completa y suficientemente precisa, podrías tener el código en cualquier lenguaje. Es lo que llamo transpile with steroids. La idea no es tomar código en Rust y rezar para que el modelo adivine el C equivalente, es tener una descripción de comportamiento tan precisa que el código, en cualquier lenguaje, se derive de ella. De la misma forma, puedes generar código a partir de un lenguaje formal como F*.

Pero aquí necesito separar dos cosas que son fáciles de confundir, porque dan garantías muy distintas. La garantía que recibes depende de dónde esté tu spec en un espectro. En un extremo hay prosa que el modelo interpreta, que es rápido, flexible, garantía cero, y la salida es plausible y necesita tests para ser confiable. En el otro extremo hay un lenguaje formal como F* o TLA*, o un sistema de tipos suficientemente rico, donde la renderización puede venir con prueba adjunta. La mayoría vivimos en el medio, y saber dónde estás en ese espectro es saber cuánto puedes confiar en la salida sin releer cada línea. El sueño de generar el sistema en cualquier lenguaje es real, la letra chica es que la fidelidad es función de la formalidad.

Alimentar el modelo es lo mismo que hacer onboarding

Creo que la mejor forma de ver al modelo es como un colaborador nuevo. Competente, rápido, incansable, y sin absolutamente ningún contexto implícito sobre nuestro dominio. Y ahí se vuelve obvio: lo que le entregas a una persona nueva en el primer día es exactamente lo que le falta al modelo, es decir, arquitectura documentada, requisitos funcionales y no funcionales escritos, guía de estilo, suite de tests sirviendo como contrato. Cuando no tienes eso, cada interacción con el modelo repite el costo del onboarding, una y otra vez. Por eso digo que, sin especificación, seguimos quemando tiempo en vez de quemar tokens.

Y hay una versión más grave de la misma enfermedad, que es cuando la empresa escribe sistemas como extensión de personas. Incluso teniendo el sistema, sigue dependiendo de individuos específicos, con conocimiento específico, para que la cosa siga funcionando. Eso para mí es un error grotesco, porque la razón de tener un sistema es justamente la opuesta, es automatizar y liberar al humano para hacer todas las otras cosas. Un sistema que encadena a su propia operación a la persona que lo entiende hizo lo contrario de lo que prometió, es un proceso manual disfrazado de software. Y nota que el conocimiento que mantiene vivo a ese sistema vive en cabezas, no en texto, que es el mismo conocimiento que falta en la spec, que el nuevo contratado no tiene y que el modelo no puede leer. Es el mismo agujero visto desde tres ángulos, y escribir ese conocimiento resuelve los tres de una vez.

La arquitectura es una elección, y hoy quedó barata

Otra cosa que creo que vale la pena repensar ahora es la arquitectura. Siempre fue una elección, solo que una elección cara de hacer bien, y por ser cara terminaba siendo postergada. Toma un sistema financiero, que es el ejemplo que más me gusta. Modelar con orientación a eventos no es moda, es fidelidad al mundo real, porque el mundo ocurre en eventos a lo largo de una línea de tiempo, y el dinero es literalmente una secuencia de asientos, no un número. Si guardas solo el último estado, como el saldo final de la cuenta corriente, tiraste la historia. Si guardas el flujo de eventos, el estado actual se vuelve una proyección que recalculas cuando quieras, y de regalo obtienes un sistema capaz de viajar en el tiempo y en la información, es decir, reconstruir cualquier estado pasado, auditar, reprocesar, responder preguntas que ni siquiera habías hecho cuando escribiste el código. Es un sistema rico en vez de un sistema amnésico.

Y este tipo de decisión, elegir entre event sourcing, CQRS, orientación a eventos, modelo de actores o un buen y viejo monolito, es exactamente lo que vale la pena hacer de forma deliberada, al principio, usando el modelo. Especialmente porque tiene un marco gigantesco de patrones para elegir y pone las alternativas y los tradeoffs sobre la mesa en minutos. Esto ataca una de las muertes más comunes del software, que es el sistema que nace con buena intención y termina como un gran monolito que quería ser unos microservicios. Esto pasa por varios motivos, y uno de ellos es confundir lo que iba a ser un MVP o prueba de concepto con un producto terminado, y entonces el andamio que estaba solo para probar una idea se vuelve estructural y nadie vuelve a cambiarlo. Cuando usamos modelo, la fase de MVP y prueba de concepto se vuelve mucho más robusta, se puede renderizar un prototipo descartable pero bien estructurado, o incluso explorar dos arquitecturas de verdad antes de comprometerse. La excusa clásica, “es solo un MVP, después lo hacemos bien”, pierde fuerza cuando hacerlo bien cuesta tokens en vez de meses. El único cuidado, y necesito decir esto, es que más opciones también es más soga para ahorcarse, porque el modelo construirá felizmente una catedral de event sourcing para un CRUD que no la necesitaba. El marco solo vale cuando viene con juicio sobre lo que el dominio realmente pide.

SOLID, Clean Code y hexagonal siguen valiendo, por una razón nueva

Vale también hablar de SOLID, Clean Code y arquitectura hexagonal, porque creo que estas cosas son buenas y a veces olvidamos por qué. Existen porque hacían la comunicación de código dentro del equipo mucho más dinámica y asertiva, traían robustez a la tecnología y evitaban la deuda técnica y el desgaste natural de los sistemas. En el fondo, eran en buena parte una disciplina para la cognición humana, porque solo podemos sostener tanto en la cabeza, entonces desacoplamos, nombramos bien y aislamos responsabilidades para poder razonar sobre un pedazo sin cargar el sistema entero junto.

El modelo sigue estos principios con naturalidad y a bajo costo, porque son algunos de los patrones mejor representados en lo que aprendió, y porque se pueden expresar como restricciones que él honra cuando se lo pides. Y aquí está la parte que me parece no obvia: la razón por la que estos principios son buenos cambia, y se vuelven más valiosos, no menos. El bajo acoplamiento y las fronteras claras eran la forma en que el humano razonaba localmente, y ahora son también las costuras por donde repartes el trabajo para el modelo y verificas cada pedazo aisladamente. La arquitectura hexagonal, con sus puertos y adaptadores, te entrega justamente las unidades delimitadas y testeables que la generación automática ama. Es decir, el encuadre sale de legibilidad, que era para el humano leer, y va a verificabilidad, que es para que tú generes y chequees por pedazos. Mismos principios, razón actualizada.

Todavía necesito ser honesto en dos puntos

Antes de cerrar, dos puntos que no quiero barrer debajo de la alfombra. El primero es que hay una circularidad incómoda: escribir una buena spec de un sistema grande y desordenado exige el mismo entendimiento profundo que el review me daba, así que el cuello de botella no desaparece, se desplaza hacia adelante. La diferencia es que pagas una vez y cosechas siempre, en vez de pagar en cada pull request, por eso para mí vale la pena, pero es una inversión, no un atajo. El segundo es que los tests solo cubren lo que imaginaste testear, y el review humano atrapaba lo que nadie especificó, especialmente en los requisitos no funcionales como rendimiento, seguridad y comportamiento bajo falla parcial, que son mucho más difíciles de capturar en test que los funcionales. Meterlos a los dos en el mismo balde sería engañarme.

El Test de Joel a los 26 años

Finalmente, creo que vale la pena volver sobre el Test de Joel, que ahora ya es un adulto de 26 años. En 2000, Joel Spolsky publicó doce preguntas binarias para medir en tres minutos si un equipo de software era serio, y sigue casi todo válido, solo que fue escrito para un mundo en que el lector del código era siempre humano. Se puede mantener la misma mecánica y medir otra cosa, que es si tu organización está lista para desarrollar con modelo:

  1. ¿La arquitectura actual está escrita en texto que un modelo puede consumir por entero?
  2. ¿Existe una suite de tests ejecutable que cubre los caminos de falla, y no solo los de éxito?
  3. ¿Los requisitos funcionales y los no funcionales están escritos en algún lugar?
  4. ¿El estilo de código es impuesto por herramienta, como formatter, linter y tipos, en vez de gusto personal?
  5. ¿Un agente puede correr build y tests con un único comando, de forma aislada?
  6. ¿Existe una forma de entregarle al modelo la porción correcta del código sin ahogarlo en contexto?
  7. ¿Puedes evaluar la salida de forma objetiva, tests pasan, tipos cierran, propiedades probadas, antes de mirar con criterio subjetivo?
  8. ¿La especificación es tratada como fuente de la verdad y el código como artefacto?
  9. ¿Existe un registro de las decisiones de arquitectura, con el porqué y no solo el qué?
  10. ¿La revisión humana ocurre sobre la spec y los tests, y no sobre cada línea generada?
  11. ¿Un colaborador nuevo, humano o modelo, llega al primer commit útil solo con lo que está escrito?
  12. ¿Mides el éxito por tokens gastados hasta el verde, y no por horas de review?

Al igual que en el test original, apuesto a que la mayoría de los equipos va a sacar dos o tres, y para mí ese es exactamente el punto. La diferencia bonita es que en el Test de Joel las preguntas eran sobre disciplina para que los humanos colaboren entre sí, y aquí son sobre hacer legible nuestro dominio a una inteligencia que no comparte nuestro contexto implícito.

Lo que queda

Creo que estamos en un momento de reflexión profunda, y vale la pena sumergirse en el paradigma de cómo abordamos al modelo y cómo lo traemos adentro del dominio. Debería ser alimentado con la mayor cantidad de especificación posible para estar alineado con la forma en que esperamos que un sistema se comporte, y ese es el mismo enfoque de cómo deberíamos hacer el onboarding de un colaborador nuevo. Es el mismo problema de siempre, especificación, solo que ahora con una consecuencia económica directa y medible. Antes, la falta de spec era una deuda silenciosa que aparecía meses después en un bug. Ahora aparece en el momento, en cada ciclo con el modelo, como tiempo humano gastado compensando un documento que nunca se escribió. La buena noticia es que ese esfuerzo no es un parche para contentar a la máquina, es lo mismo que vuelve el sistema comprensible para quien llegue después. La diferencia es que ahora tengo una razón barata e inmediata para hacer finalmente lo que ya debería estar hecho. Sin eso, seguimos quemando tiempo en vez de tokens.

Referencia

Joel Spolsky, “The Joel Test: 12 Steps to Better Code”, 2000. Disponible en https://www.joelonsoftware.com/2000/08/09/the-joel-test-12-steps-to-better-code/.