Capítulo 02 de 10 · 9 min de lectura
Qué es SDD: la spec es el contrato
Las decisiones se escriben antes que el código. Suena a burocracia; es lo que las convierte en algo que se puede leer, revisar y exigir.
Del encargo al contrato
El capítulo anterior terminaba con veintiuna decisiones que nadie había tomado y varios programas que las tomaban por ti. SDD, Spec-Driven Development o desarrollo guiado por especificaciones, es la respuesta más directa a eso: las decisiones se escriben antes que el código, en un fichero que vive en el repositorio. A ese fichero se le llama especificación, o spec.
La palabra asusta porque recuerda a documentos de cuarenta páginas que nadie abre. No es eso. Toda la especificación de acorta, el proyecto que acompaña al tutorial, son ocho ficheros de texto y unas seiscientas líneas.
Tampoco es un documento sobre el programa, de los que se escriben al final y envejecen solos. Es el contrato que el programa tiene que cumplir. Si el código y la spec dicen cosas distintas, hay un fallo, y se arregla uno de los dos.
Qué pinta tiene
Este es un trozo real de la spec de acorta: el que dice qué alias puede elegir un usuario para su enlace.
- **ALI-02** — Dado un alias de menos de 3 o de más de 32 caracteres, entonces error
`alias: debe tener entre 3 y 32 caracteres`. Con 3 y con 32 se acepta. Se cuentan
caracteres, no bytes: `ñu` tiene 2 y da este error; `ñus` tiene 3 y da el de ALI-03.
- **ALI-03** — Dado un alias con algún carácter que no sea `a-z`, `0-9` o `-` (`Oferta`,
`mi_enlace`, `año-nuevo`, `a b c`), entonces error `alias: solo admite minúsculas,
números y guiones`. Las mayúsculas no se convierten.
- **ALI-04** — Dado un alias que empieza o termina por guion (`-oferta`, `oferta-`),
entonces error `alias: no puede empezar ni terminar por guion`.
- **ALI-05** — Dado un alias que es una palabra reservada (`api`, `assets`), entonces
error `alias: "api" está reservado`.
Tres cosas la separan de una descripción:
- Cada frase se puede comprobar. No dice que el alias «debe ser válido». Dice qué pasa con dos caracteres, con tres, con treinta y dos y con treinta y tres. Alguien, o algo, puede sentarse y verificarlo.
- Dice qué responde el programa cuando algo va mal. El mensaje de error está escrito letra a letra, porque también es una decisión, y si no la tomas tú la tomará el modelo.
- Cada criterio tiene nombre.
ALI-03parece un detalle de oficinista, pero permite que un test diga «yo compruebo ALI-03» y que una revisión diga «falta el test de ALI-05».
Una spec dice también lo que no se va a hacer. La de acorta tiene una sección, «Fuera de la v1», con cosas como estas:
- Editar un enlace ya creado (se borra y se crea otro).
- Detección de bots o de visitas repetidas.
- Deduplicación: acortar dos veces la misma URL da dos enlaces distintos.
Sirve para lo contrario de lo que parece. No es una lista de carencias: es la forma de que un agente no añada algo porque le parece razonable. En el experimento del capítulo anterior, uno de los programas traía caducidad de enlaces sin que nadie la hubiera pedido.
Las palabras que esconden decisiones
Escribir así no sale solo. Lo que sale solo es esto, que es el mismo trozo de spec escrito a la primera:
El usuario puede elegir un alias para su enlace. El alias debe ser válido y no demasiado largo. Normalmente irá en minúsculas. No se permiten caracteres raros, espacios, etc. Si el alias ya existe se mostrará un error adecuado. Algunos alias deberían estar reservados.
- Palabra vaga¿Qué significa «válido» exactamente? Escribe la regla.
- Cantidad sin número«demasiado»: ¿cuánto? Falta un número.
- Obligación a medias«Normalmente»: ¿es obligatorio o no? Una spec dice lo que pasa, no lo que estaría bien.
- Palabra vaga¿Qué significa «raros» exactamente? Escribe la regla.
- Lista sin cerrar«etc.»: ¿qué más entra? Una lista abierta la termina quien implementa.
- Error sin definir¿Qué error exactamente? Escribe el mensaje y cuándo sale.
- Palabra vaga¿Qué significa «adecuado» exactamente? Escribe la regla.
- Cantidad sin número«Algunos»: ¿cuánto? Falta un número.
- Obligación a medias«deberían»: ¿es obligatorio o no? Una spec dice lo que pasa, no lo que estaría bien.
Ninguna de las palabras que busca el detector. Eso no significa que la spec esté completa: solo que no deja decisiones a medias a la vista.
El detector es tonto a propósito. No entiende el texto: busca una lista de palabras que casi siempre significan «esto todavía no lo he decidido». Válido, adecuado, demasiado, normalmente, etc. Cada una es una pregunta que le estás dejando a quien implemente, y quien implementa ahora es un modelo que no pregunta.
Cambia a «Como quedó en acorta» y fíjate en que el texto es más largo y, sin embargo, no deja ninguna de esas palabras. Después escribe una frase tuya, de algo que tengas a medias, y mira cuántas salen.
Cuatro reglas
Un fichero de texto no es un contrato por estar en el repositorio. Lo es cuando todo el mundo, personas y agentes, trabaja con cuatro reglas. En acorta están en AGENTS.md, el fichero que cada agente lee antes de empezar:
SDD:
specs/es el contrato. Leespecs/001-plan-tecnico.mdy la spec de tu fase antes de escribir código. Nada se implementa si no está especificado. Si tu cambio contradice una spec, no la ignores: actualízala en el mismo cambio y dilo en el informe.
Desplegadas:
- Nada se implementa si no está especificado. Ante una funcionalidad nueva, lo primero que se escribe es la spec, no el código.
- Antes de tocar código, se lee la spec. Todos los encargos que recibieron los agentes de acorta empiezan por ahí. El del almacén, por ejemplo: «Antes de escribir nada lee, en este orden:
AGENTS.md,specs/001-plan-tecnico.mdyspecs/003-almacen.md». - Si el código contradice la spec, la spec cambia en el mismo cambio. Una spec desactualizada es un fallo, igual que un test roto.
- Un cambio deja coherentes todas las specs que toca. Si una decisión nueva afecta a tres ficheros, se cambian los tres.
La tercera es la que más cuesta cumplir, así que conviene verla en un caso real. El plan técnico de acorta decía que las rutas del servidor se repartirían con el enrutador de la biblioteca estándar. El agente que escribió el servidor decidió hacerlo a mano, por buenos motivos, y todos sus tests pasaban. Se aceptó, pero el commit no lleva solo código:
57045db server: API, redirección y estáticos (verde)
bitacora/005-fase-4-api.md | 36 +++++++++++
server/api.go | 157 +++++++++++++++++++++++++++++++++++++++++++++
server/redirect.go | 30 +++++++++
server/respond.go | 50 +++++++++++++++
server/server.go | 70 +++++++++++++++++---
server/static.go | 57 ++++++++++++++++
specs/001-plan-tecnico.md | 2 +-
specs/004-api.md | 4 +-
8 files changed, 395 insertions(+), 11 deletions(-)
Las dos líneas de specs/ son la regla 3. Sin ellas, el siguiente agente habría leído un plan técnico que describe un programa que ya no existe.
Cambiar de idea cuesta minutos
La razón práctica para escribir antes es que es el momento barato para equivocarse.
Con la spec de acorta ya escrita y sin una sola línea de código, llegó la revisión humana. Una de las decisiones que el agente había tomado por defecto era guardar en Git la versión compilada de la interfaz web. La pregunta de Xavi fue esta:
Frontend compilado en Git, es lo mejor?
No lo era. Lo que hubo que cambiar, copiado de la bitácora del proyecto:
| Spec | Cambio |
|---|---|
| 001 | server/static/dist/ va en .gitignore; make build compila siempre el frontend; un clon sin compilar sigue construyendo y pasando los tests |
| 004 | El servidor recibe el sistema de ficheros de los estáticos en vez de usar siempre el embebido. Criterios EST reescritos y dos nuevos: EST-04 (página de aviso si el frontend no está compilado) y EST-05 (el aviso sí está en Git) |
| 006 | El build de Vite escribe en server/static/dist/ |
| 007 | La fase 0 ya no crea un index.html provisional; la fase 4 es dueña de todo server/; la fase 6, solo de web/ |
Cuatro ficheros de texto y unos minutos. La misma decisión, tomada con el servidor y la interfaz ya escritos, habría tocado código, tests, el Makefile y el historial.
La memoria del proyecto
Hay otra razón, que solo aparece cuando trabajas con agentes. Un agente empieza cada tarea sin recordar nada. La conversación donde decidiste que los alias van en minúsculas se pierde al cerrarla, y el agente de mañana no estuvo allí.
acorta lo implementaron seis agentes distintos, uno por pieza. Ninguno vio la conversación en la que se decidió qué era acorta. No les hizo falta: lo que necesitaban saber estaba en specs/, y fue lo primero que leyeron.
El conocimiento del proyecto vive en el repositorio, no en la memoria de nadie. Eso vale igual para una persona que llega nueva, o para ti dentro de tres meses.
Lo que una spec no hace
Sería deshonesto dejarlo ahí. Los criterios reales de arriba pasan el detector sin un solo aviso, y aun así estaban incompletos. Así cambió uno de ellos poco después de aprobarse la spec (las líneas están partidas para que quepan):
- ALI-02 — Dado un alias de menos de 3 o de más de 32 caracteres, entonces error
- `alias: debe tener entre 3 y 32 caracteres`. Con 3 y con 32 se acepta.
+ ALI-02 — Dado un alias de menos de 3 o de más de 32 caracteres, entonces error
+ `alias: debe tener entre 3 y 32 caracteres`. Con 3 y con 32 se acepta. Se cuentan
+ caracteres, no bytes: `ñu` tiene 2 y da este error; `ñus` tiene 3 y da el de ALI-03.
«Entre 3 y 32 caracteres» parecía imposible de malinterpretar. Pero ñu, ¿son dos caracteres o tres bytes? Ningún detector de palabras ve ese hueco, y la revisión humana tampoco lo vio. Lo encontró el agente que tuvo que escribir el test, porque un test obliga a decidir el resultado exacto.
No fue un caso aislado. Después de aprobarse, la spec de acorta volvió a cambiar en nueve commits más, casi todos con el mismo origen: alguien, al escribir un test, hizo una pregunta que el texto no contestaba.
Una spec no se escribe una vez ni sale perfecta. Lo que hace es darle a cada hueco un sitio donde aparecer y un sitio donde arreglarse, que no es el código.
Lo que viene
Queda la objeción obvia: escribir todo esto parece mucho trabajo, y hay que saber qué preguntas hacerse. En el capítulo siguiente no lo escribes tú solo. Le das la vuelta al experimento del capítulo 1: en vez de dejar que el modelo decida en silencio, le pides que te pregunte.