Intención, especificación e implementación

Enterate del próximo post
Un mail cuando publico algo nuevo. Sin spam y te podés dar de baja cuando quieras.

Un mail cuando publico algo nuevo. Sin spam y te podés dar de baja cuando quieras.
El primer post de esta serie mostró un mismo pedido escrito de dos formas: como se le suelta a un agente sin pensarlo, y como una especificación mínima de tres líneas. La diferencia entre esas dos versiones no es solo de extensión. La spec de tres líneas resuelve, sin decirlo, una pregunta distinta de la que resuelve el pedido original, y esa pregunta es a su vez distinta de una tercera que todavía no apareció en la serie. Separar esas tres preguntas es, con diferencia, el hábito más útil para empezar a escribir especificaciones.
Toda funcionalidad tiene tres capas, y cada una responde una pregunta distinta.
La intención es la capa más alta: qué se quiere lograr y para quién. Todavía no menciona el sistema: describe la necesidad real que existe detrás del pedido. "Quien usa la aplicación encuentra cosas que quiere leer más tarde y no tiene tiempo de leer ahora" es una intención. Nombra a una persona y un problema, no un comportamiento que se pueda comprobar.
La especificación traduce esa intención en algo que el sistema tiene que cumplir, de forma verificable. "El sistema guarda un artículo a partir de su URL" ya no habla de la persona: habla del sistema, y lo hace en términos que se pueden observar desde afuera. Esta es la capa donde vive lo que la serie llama, de acá en adelante, especificación o spec.
La implementación es cómo el sistema cumple lo especificado: qué lenguaje, qué estructura de datos, qué consulta, qué algoritmo. Dos implementaciones completamente distintas, una tabla con una restricción de unicidad, o una lista en memoria que se recorre entera en cada guardado, pueden cumplir exactamente la misma especificación. Eso es lo que separa a las tres capas entre sí: la intención admite muchas especificaciones razonables, y una especificación admite muchas implementaciones razonables. Bajar de capa a propósito es una decisión; bajar de capa sin haberlo notado es el error que este post nombra.
El error más común de quien empieza a escribir especificaciones no es escribir de más ni de menos: es completar la especificación con implementación disfrazada. No por descuido, sino porque pensar en cómo se resuelve algo es más concreto que pensar en qué tiene que cumplirse, y la atención salta ahí primero. El síntoma se reconoce igual en cualquier proyecto: una frase que nombra una tabla, una consulta, una función o un formato de archivo concreto, adentro de un documento que se supone que solo dice qué, no cómo.
El post anterior mostró una spec de tres líneas para la funcionalidad de guardar un artículo en la Bitácora de lecturas, sin decir en qué capa vivía cada línea. Las tres eran, de hecho, especificación pura: describían un comportamiento observable, guardar a partir de una URL, avisar si ya existe, guardar título y fecha además de la URL, sin tocar ninguna decisión técnica. Pero el post no lo señalaba, y sin señalarlo, esa distinción es fácil de perder la próxima vez que se escribe una spec apurada.
Este post retoma la misma funcionalidad y agrega, antes y después de esa especificación, las dos capas que faltaban: la intención que la motiva, y un ejemplo concreto de la implementación que alguien podría elegir para cumplirla, marcado de forma explícita como algo que no pertenece al documento de especificación. Así la frontera entre las capas queda visible en un caso real, no solo enunciada en abstracto.
# Guardar un artículo: intención, especificación e implementación
## Intención
Quien usa la Bitácora encuentra artículos que quiere leer más tarde y no
tiene tiempo de leer en el momento. Necesita guardarlos sin perder el
título ni la referencia de dónde los encontró, y sin terminar con el
mismo artículo guardado dos veces por error.
## Especificación
- Un artículo se guarda a partir de su URL.
- Si esa URL ya fue guardada antes, no se guarda de nuevo: se avisa que
ya existe.
- Además de la URL, se guarda el título de la página y la fecha en la
que se guardó.
## Implementación (no pertenece a este documento)
- Comparar la URL nueva contra las guardadas normalizando mayúsculas y
quitando la barra final, antes de buscar una coincidencia exacta.
- Persistir cada artículo en una tabla `articulos` con una columna `url`
con restricción `UNIQUE`, y dejar que el motor de base de datos
rechace el duplicado.
- Obtener el título de la página haciendo un `fetch` de la URL y leyendo
el contenido de la etiqueta `<title>` del HTML devuelto.La especificación es palabra por palabra la del post anterior: separarla de las otras dos capas no le cambió una sola línea, porque ya vivía en la capa correcta desde el principio. Lo que cambia es la sección de implementación, y el motivo de incluirla no es prescribir cómo construir la Bitácora, ninguna de esas tres decisiones vuelve a aparecer en el resto de la serie, y podrían resolverse de otra forma sin que la especificación se entere, sino mostrar, con un ejemplo concreto, la clase de frase que no tiene que aparecer en un documento de especificación, aunque sea perfectamente razonable en un documento de diseño técnico.
Sin la separación entre las tres capas, la especificación de una funcionalidad tiende a llenarse con frases del tipo de las que aparecen en la sección de implementación de arriba. Ninguna de esas frases está mal en sí misma: el problema es dónde termina viviendo cada una.
Si la especificación de guardar un artículo dijera "comparar la URL contra la tabla articulos con una consulta que ignore mayúsculas" en vez de "si esa URL ya fue guardada antes, no se guarda de nuevo", estaría fijando una decisión, que existe una tabla llamada articulos, que la comparación se hace con una consulta a esa tabla, que la intención original nunca pidió. Un agente que reciba esa versión de la especificación pierde el margen de elegir, por ejemplo, guardar los artículos en un archivo en vez de en una base de datos, aunque esa alternativa sea perfectamente compatible con lo que la persona que usa la Bitácora necesita en realidad. La especificación dejó de decir qué y empezó a decir cómo, sin que nadie lo haya decidido a propósito.
El problema simétrico también existe del lado de quien revisa. Una especificación mezclada con implementación es más lenta de evaluar, porque cada línea exige preguntarse primero a qué capa pertenece antes de poder juzgar si es correcta. Alguien que revisa la spec de guardar un artículo para confirmar que cubre los casos que importan termina evaluando si una restricción de unicidad en la base de datos es la forma correcta de evitar duplicados, en vez de evaluar si evitar duplicados es, en primer lugar, lo que hay que hacer. Son dos preguntas distintas, y mezclarlas en el mismo documento dificulta responder cualquiera de las dos.
Distinguir intención de especificación tiene un costo simétrico y menos discutido: una especificación que se queda en el nivel de la intención ("la aplicación tiene que ser fácil de usar para guardar artículos") no le deja al sistema nada verificable, y devuelve el problema a donde empezó, con la diferencia de que ahora parece que ya se resolvió.
Intención, especificación e implementación responden tres preguntas distintas, para qué, qué y cómo, y cada una admite muchas respuestas razonables en la capa de abajo. Escribir una en el lugar de otra no ahorra trabajo: fija una decisión donde todavía no correspondía fijar ninguna.
La especificación de guardar un artículo, aislada de las otras dos capas, ya parece completa: dice qué pasa con una URL repetida y qué datos se guardan además de la URL. El próximo post de la serie muestra que el método que hoy se llama Spec Driven Development no nació en 2025: retoma ideas de los contratos de Hoare, del diseño por contrato de Meyer y del desarrollo guiado por comportamiento, todas anteriores por décadas, para distinguir qué hay de nuevo de verdad y qué es una forma distinta de nombrar algo que ya se sabía hacer.