Tensión en el Diseño de Software: Por qué especificar más no es especificar mejor

En la ingeniería de software tradicional nos refugiamos en la seguridad de las reglas estrictas: bajo acoplamiento, alta cohesión, normalización hasta tercera forma normal, SOLID como mandamiento. Durante décadas, algunas de estas técnicas fueron murallas contra el caos. Y tenían sentido, cuando nuestro único interlocutor era una cadena de herramientas determinista: el compilador, el linter, el análisis estático.
Pero algo cambió. Los modelos de lenguaje avanzados (GPT, Claude, Gemini y los que vendrán) no son chequeos estáticos. Son razonadores probabilísticos con una capacidad notable para navegar ambigüedad. Y cuando los tratamos como si fueran un pipeline de validación — dictándoles restricciones herméticas — obtenemos exactamente lo que merecemos: código que cumple las reglas pero tal vez falla en resolver el problema (de negocio).
La verdadera maestría no está en dictar leyes inquebrantables. Está en manifestar tensiones conscientes.
La cárcel de prompts: un antipatrón
Muchos desarrolladores y líderes técnicos senior, acostumbrados a escribir specs rigurosas para equipos humanos, replican el mismo patrón con modelos de lenguaje. El prompt se convierte en una cárcel de restricciones:
Escribe este módulo siguiendo estrictamente SOLID,
sin librerías externas, con cobertura de tests del 90%,
usando solo inyección de dependencias por constructor,
sin clases abstractas.El resultado será funcional, pero también podría llegar a ser mediocre. ¿Por qué?
Porque cada restricción reduce el espacio de soluciones que el modelo puede explorar. Al apilar restricciones, no estás guiando al modelo hacia la mejor solución — lo estás forzando a optimizar para tus reglas, y tal vez no para tu problema. Es la diferencia entre decirle a un arquitecto “diseñame una casa” y decirle “diseñame una casa sin ventanas, sin madera, sin más de un piso y sin curvas”. Técnicamente va cumplir, pero le quitaste toda capacidad de diseño.
Fred Brooks, en su célebre paper “No Silver Bullet — Essence and Accident in Software Engineering” (1986), distinguía entre complejidad esencial — la inherente al problema que estás resolviendo — y complejidad accidental — la que introducimos nosotros mismos con nuestras herramientas y procesos. Las reglas estrictas en prompts son, frecuentemente, generadores de complejidad accidental. Le estás pidiendo al modelo que resuelva tu problema y además que navegue un laberinto de restricciones que quizás no tienen relación con el problema real.
El debate de la especificación: SDD y sus matices
Este antipatrón no se limita a los prompts individuales. Actualmente la industria vive una versión a escala del mismo dilema: el auge del spec-driven development (SDD).
La promesa es seductora: dejá de “vibe codear”, escribí una especificación formal, y dejá que el agente de IA la ejecute. SpecKit acumuló 77,000 estrellas en GitHub. AWS construyó un IDE completo alrededor del concepto (Kiro). Tessl levantó 125 millones de dólares apostando a que las specs — no el código — debían ser la fuente de verdad.
Pero los resultados no siempre acompañan la promesa. Scott Logic publicó un estudio donde el enfoque resultó diez veces más lento, con más ceremonia y los mismos bugs. Alvis Ng, en su artículo “Spec-Driven Development Is Waterfall in Markdown” (Medium, marzo 2026), comparó el SDD con la misma apuesta que la industria hizo con el outsourcing: la creencia de que si documentás algo con suficiente detalle, alguien (o algo) del otro lado lo ejecutará perfectamente.
Ahora bien, el problema no es la especificación en sí. Las specs pueden ser herramientas valiosas. El problema aparece cuando se tratan como contratos cerrados — documentos exhaustivos que el agente debe ejecutar al pie de la letra — en lugar de como guías que dejan espacio para el razonamiento.
Una spec que dice “implementar autenticación OAuth2 con refresh tokens, manejo de expiración y revocación” es útil. Una spec que además dice “el sistema va a recibir tráfico esporádico en ráfagas de 10K usuarios simultáneos durante eventos de venta, pero el resto del tiempo es casi idle, y buscamos que la solución sea económica sin sacrificar la experiencia en esos picos” es mucho más útil. La primera describe el qué. La segunda captura el por qué y el en qué contexto — y eso es lo que le permite al modelo tomar decisiones de diseño inteligentes.
La calidad del lenguaje técnico importa
Hay otro factor que el debate sobre SDD tiende a pasar por alto: la calidad del lenguaje técnico en las especificaciones. No alcanza con escribir más. Hay que escribir bien.
Un modelo de lenguaje es, en última instancia, un sistema que opera sobre texto. La precisión de su output está directamente relacionada con la precisión de su input. Una spec que dice “hacer que la API sea rápida” va a producir resultados genéricos. Una spec que dice “el p99 de latencia del endpoint /checkout no debería superar los 300ms bajo carga de 500 requests/segundo" le da al modelo algo concreto contra lo cual razonar, planificar y hasta ejecutar de forma autónoma.
Esto aplica también al vocabulario de diseño. No es lo mismo decir “que los módulos estén bien separados” que decir “que el módulo de facturación pueda desplegarse independientemente del módulo de inventario”. La primera es una aspiración vaga; la segunda es una restricción de arquitectura con implicancias concretas sobre contratos de API, persistencia y deployment.
Las specs mal escritas (imprecisas, ambiguas, con jerga difusa) no se arreglan agregando más detalle. Se arreglan con mejor pensamiento técnico traducido a mejor lenguaje técnico. Si la especificación no está bien articulada, ni el mejor modelo del mundo va a inferir lo que quisiste decir.
Mi enfoque: specs con intuición experta, no specs a rajatabla
En la práctica, yo no descarto las especificaciones. Pero tampoco las sigo a rajatabla.
Lo que hago es algo más parecido a escribir historias de usuario informadas por una intuición de cómo funciona el modelo particular con el que estoy trabajando. Después de un tiempo de trabajar con un modelo, empezás a desarrollar una noción de qué tipo de indicaciones producen mejores resultados: dónde conviene ser específico y dónde conviene dejar margen, qué vocabulario técnico el modelo maneja con más fluidez, qué tipo de contexto de negocio lo ayuda a tomar mejores decisiones de diseño.
Mis specs no son documentos exhaustivos que pretenden cubrir cada caso borde. Son más bien guías de intención que intentan ser “AI-friendly”, por lo cual están escritas pensando en cómo el modelo va a procesarlas, y en total contraposición a cómo un humano las leería.
Y después de la generación, no hago code review en el sentido tradicional. Lo que hago es un loop de aceptación: evalúo si el resultado resuelve la tensión que planteé, pruebo los caminos críticos, y si algo no cierra, ajusto la guía y vuelvo a iterar. Es un ciclo más parecido a un diálogo que a una inspección.
No espero magia. Pero tampoco pretendo controlar cada línea. El punto es participar activamente en un proceso donde la especificación es un punto de partida maleable, no un contrato fijo.
La tensión como herramienta de diseño
Lo que sí cambió radicalmente en mi flujo de trabajo es cómo especifico. En lugar de reglas prohibitivas, trato de expresar tensiones explícitas.
Una tensión es un par de fuerzas en conflicto que el modelo debe resolver. Es decirle: “estas dos cosas importan, y están en contradicción — encontrá el equilibrio”.
Enfoque por reglas:
Normaliza hasta 3NF. No uses JSON columns.
Índices solo en primary keys y foreign keys.Enfoque por tensión:
Diseñá este esquema sabiendo que hoy tenemos un millón de registros,
pero es probable que escalemos a diez millones en un año.
No quiero sobre-ingeniería prematura, pero necesito que el camino
hacia el sharding sea evidente en el diseño.En el segundo caso no hay una regla prohibitiva. Hay una proyección de carga y una dirección de evolución. El modelo entiende que la solución óptima vive en el equilibrio entre simplicidad actual y escalabilidad futura. Puede decidir desnormalizar una tabla si eso simplifica el path hacia el sharding, o puede mantener la normalización si el volumen no lo justifica. La decisión queda en manos del razonador, que es exactamente donde debería estar.
Ejemplo 2: Rendimiento vs. mantenibilidad
Enfoque por reglas:
Optimizá para O(n log n) o mejor.
No uses más de 50 líneas por función.Enfoque por tensión:
Optimizá este algoritmo para latencia, pero asumiendo que el equipo
de mantenimiento es junior. Si la micro-optimización hace el código
incomprensible, prefiero 20ms más de latencia y un código que se
autodocumente.Acá le estás dando al modelo algo que las reglas no pueden capturar: una jerarquía de valores con un umbral de tolerancia. El modelo puede decidir que la micro-optimización vale la pena si el costo en legibilidad es menor de lo esperado, o puede elegir la solución más clara si el delta de performance es marginal.
Ejemplo 3: Desacoplamiento pragmático
Enfoque por reglas:
Todos los módulos deben comunicarse por interfaces.
Cero dependencias directas entre capas.Enfoque por tensión:
Busco bajo acoplamiento, pero este feature tiene exactamente tres
casos de uso conocidos. Prefiero una solución "dry-ish" que sea fácil
de refactorizar, en lugar de una interfaz genérica abstracta que
nadie entienda hoy. Si en seis meses necesitamos generalizarla,
el refactor debería ser trivial.El desacoplamiento es un principio, no un dogma. A veces, la abstracción necesaria para desacoplar dos componentes introduce más complejidad cognitiva de la que resuelve. Al expresar esto como tensión, le das al modelo permiso para hacer un trade-off consciente en lugar de seguir la regla ciegamente.
Por qué esto funciona: las bases teóricas
Esta idea de “diseño por tensión” no es nueva. Tiene raíces profundas en el pensamiento sistémico y en la teoría del diseño de software.
Christopher Alexander y las fuerzas en conflicto
Christopher Alexander — el arquitecto cuyas ideas inspiraron los patrones de diseño en software — nunca pensó los patrones como recetas fijas. En A Pattern Language (1977) y especialmente en The Timeless Way of Building (1979), Alexander describe los patrones como resoluciones de tensiones espaciales. Cada patrón existe porque hay fuerzas en conflicto (luz vs. privacidad, circulación vs. intimidad) y el patrón es la forma de resolver ese conflicto en un contexto específico.
La comunidad de software adoptó la notación de Alexander (nombre-contexto-problema-solución) pero, como él mismo señaló en su charla en OOPSLA 1996, ignoró la esencia: que un buen diseño no aplica patrones mecánicamente, sino que resuelve las tensiones particulares de cada situación. Cuando planteamos tensiones a un modelo de lenguaje, estamos volviendo a lo que Alexander realmente quiso decir.
Richard P. Gabriel y “Worse is Better”
En 1989, Richard P. Gabriel escribió un ensayo que se volvería legendario en la ingeniería de software: “The Rise of ‘Worse is Better’” (originalmente una sección de “Lisp: Good News, Bad News, How to Win Big”). Su tesis central es provocadora: la simplicidad de implementación suele ser más importante que la perfección del diseño. Un sistema “peor” pero simple, portable y funcional tiene mejores “características de supervivencia” que un sistema perfecto pero complejo.
Por esta razón, cuando apilamos reglas estrictas en un prompt, estamos buscando “The Right Thing” — la solución perfecta que cumple todos los criterios. Pero como Gabriel argumenta, esa solución tarda demasiado en llegar y es demasiado rígida para adaptarse. Al admitir tensiones — al decirle al modelo “esto es lo ideal pero esto otro es lo práctico, encontrá el punto medio” — estamos aplicando el principio de worse is better: una solución 80% correcta ahora, que puede evolucionar, vale más que una solución 100% correcta que nadie entiende.
Gabriel mismo osciló sobre esta idea durante años — llegó a escribir un ensayo refutando su propia tesis bajo el pseudónimo “Nickieben Bourbaki”, y luego otro defendiéndola de nuevo. Esa oscilación es, en sí misma, una tensión sin resolver. Y probablemente ahí reside su poder.
Fred Brooks y la complejidad esencial
Brooks, en “No Silver Bullet” (1986), argumenta que la mayor parte de la dificultad en construir software es esencial — viene del problema mismo, no de nuestras herramientas. La complejidad accidental (la que creamos nosotros) se puede reducir con mejores lenguajes, mejores herramientas, mejores abstracciones. Pero la complejidad esencial permanece.
Las reglas estrictas en prompts atacan la complejidad accidental (formato del código, patrones a seguir, restricciones técnicas). Pero la tensión ataca la complejidad esencial: le dice al modelo cuál es el verdadero problema y qué fuerzas están en juego. Es la diferencia entre darle un manual de estilo y darle una comprensión del dominio.
Goldratt y la Teoría de Restricciones
Eliyahu M. Goldratt, en The Goal (1984), propone que todo sistema tiene al menos una restricción (un cuello de botella) que determina su rendimiento total. Su insight clave: optimizar cualquier parte del sistema que no sea la restricción es un desperdicio. Solo las mejoras en la restricción mueven la aguja.
Aplicado a nuestro contexto: cuando le damos a un modelo una lista de reglas de código (naming conventions, patrones de diseño, límites de complejidad ciclomática), estamos optimizando partes del sistema que probablemente no son la restricción real del proyecto. En cambio, si le damos el contexto del negocio — “necesitamos soportar 10M de registros”, “el equipo de mantenimiento es junior”, “este servicio no puede caer más de 5 minutos al mes” — le estamos señalando dónde está la restricción real. El modelo puede entonces subordinar todo lo demás a esa restricción, que es exactamente lo que Goldratt prescribe.
Lo que cambió en mi forma de pensar
Después de meses trabajando con modelos avanzados, noto que mis mejores resultados comparten un patrón. No fue algo deliberado al principio, sino más bien una observación retrospectiva que fui refinando con el tiempo.
La tensión aparece antes que el prompt. Antes de escribir cualquier cosa, lo que realmente me ayuda es preguntarme: ¿cuáles son las dos o tres fuerzas en conflicto acá? Performance vs. legibilidad. Flexibilidad vs. simplicidad. Velocidad de entrega vs. robustez. Si no puedo articular la tensión, generalmente es señal de que todavía no entiendo el problema lo suficiente, y probablemente ninguna cantidad de reglas va a compensar eso.
El contexto de negocio rinde más que el contexto técnico. “Este endpoint es el que usa el checkout” le dice al modelo mucho más que “latencia menor a 200ms”. El modelo puede inferir las implicancias de performance, disponibilidad y tolerancia a errores a partir del contexto de negocio. Las restricciones técnicas son baratas de especificar. La comprensión del dominio es cara, y es donde el modelo más valor agrega cuando la tiene.
Las mejores soluciones son las que no pedí. En muchas ocasiones he encontrado que los resultados que más me sorprendieron fueron aquellos donde el modelo eligió un enfoque que yo no habría considerado, pero que resolvía la tensión de manera más elegante que mi idea original. Eso solo pasa cuando el prompt tiene espacio para lo inesperado. Si cada decisión ya está tomada en la especificación, estás pagando por un razonador y usándolo como transcriptor.
La regla tiene un momento, pero no es el primero. Esto no significa que las reglas no sirvan. Sirven, pero más como guardrails al final del proceso que como directrices al principio. Primero la tensión, después la exploración, y recién entonces las restricciones duras que necesitemos imponer. El orden importa.
Conclusión: El programador como director de orquesta
En la era de los modelos de lenguaje avanzados, el rol del líder técnico cambia. Ya no se trata de ser el “policía del linting” que vigila que cada línea cumpla las reglas. Se parece más a un director de orquesta que comunica la visión del sistema como un conjunto de fuerzas en conflicto, y deja que cada instrumento, ya sea humano o artificial, encuentre la mejor forma de resolverlas.
No busques la solución perfecta que siga todas las reglas. Buscá la tensión correcta que manifieste la solución más elegante. Las reglas están ahí para servir al producto, no al revés. Saber romperlas con conciencia es lo que separa a un simple codificador de un arquitecto de sistemas.
Publicado también en Medium