API-DD

Capítulo del manifiesto

Desarrollo con IA

Contratos y tests para obtener código generado que pueda verificarse.

La IA no elimina las decisiones de diseño#

Una herramienta de IA puede explorar un repositorio, proponer una API, escribir tests e implementar código con rapidez. Esa velocidad no resuelve por sí sola qué comportamiento necesita el sistema. Si el encargo es ambiguo, el resultado puede ser técnicamente plausible y aun así resolver otro problema.

API-DD es compatible con este modo de trabajo porque ofrece una perspectiva para explicitar el límite de cada módulo: quién lo consume, qué mensajes intercambia, qué garantiza y qué mantiene oculto. No impone una arquitectura, un proceso ni una división entre el trabajo humano y el de la IA. Ayuda a convertir decisiones difusas en un contrato que ambos pueden revisar.

La diferencia práctica está en el criterio de éxito. «Genera el código para esta funcionalidad» invita a completar huecos por probabilidad. «Implementa esta API y demuestra estos casos» reduce la ambigüedad y permite evaluar el resultado por su comportamiento.

Pensar el sistema como APIs#

Antes de generar una implementación conviene describir las conversaciones afectadas. Para cada módulo basta con responder lo necesario:

| Decisión | Qué aclara para la IA | |---|---| | Consumidor | Desde qué necesidad debe diseñarse el cambio | | Mensajes | Qué operaciones, resultados y errores puede usar | | Garantías | Qué comportamiento debe conservarse | | Límite | Qué archivos y módulos pertenecen al cambio | | Detalles internos | Qué decisiones puede tomar libremente la implementación | | APIs consumidas | Qué colaboraciones existen y cuáles pueden sustituirse en un test |

Este mapa reduce dos fallos frecuentes. El primero es ampliar el cambio con abstracciones que nadie pidió. El segundo es copiar detalles del mecanismo en la API: nombres de una librería, estructuras de persistencia o pasos internos que luego quedan convertidos en contrato.

No todos los huecos deben rellenarse antes de empezar. Algunos se descubren al investigar el código o al escribir el primer test. Lo importante es reconocer qué es una decisión pendiente en vez de dejar que una respuesta generada la tome de forma accidental.

Los casos de prueba concretan el resultado#

Un caso de prueba expresa una diferencia observable: una entrada, una acción y un resultado, estado o mensaje que importa al consumidor. Al ejecutarlo se convierte además en feedback para la persona y para la IA.

Un buen conjunto de casos cubre las diferencias relevantes sin repetir la misma regla con datos decorativos. Por ejemplo:

dado un búfer vacío con capacidad uno
cuando se añade un valor
entonces el valor queda disponible

dado un búfer lleno
cuando se intenta añadir otro valor
entonces informa que está lleno y conserva el primero

Estos casos dicen más que una petición genérica de «manejar errores». También dejan libertad para usar una lista, un arreglo circular u otra representación. El test protege el contrato; no dicta el recorrido interno.

Ver el test fallar antes de implementar aporta una evidencia sencilla: la prueba puede detectar la ausencia del comportamiento. Verlo pasar después confirma que esa implementación satisface el ejemplo. Ninguna de las dos señales demuestra por sí sola que el diseño esté completo, pero juntas son más fiables que aceptar código porque parece correcto.

Spec-Driven Development con la especificación en el código#

API-DD es compatible con Spec-Driven Development. En esta forma de aplicarlo, la especificación ejecutable no es un documento externo que la implementación deba interpretar: es el archivo —o el conjunto— de tests versionado junto al código de producción.

La documentación puede explicar contexto y motivaciones. La parte del contrato que debe aceptar la implementación queda en código ejecutable: API pública, casos relevantes, resultados esperados y límites. El recorrido interno queda fuera.

Esto acerca la especificación al lugar donde puede contradecirse. Si cambia la firma de la API, el test deja de compilar. Si cambia una garantía, el test falla. El repositorio conserva juntos el contrato ejecutable, la implementación y la historia de ambos.

También permite separar el trabajo según la dificultad de cada etapa:

intención + repositorio
        ↓
modelo con mayor capacidad de razonamiento
        ↓
API + casos + tests en rojo + límites explícitos
        ↓
modelo más simple, acotado por esa especificación
        ↓
implementación + tests en verde

La fase de especificación concentra la ambigüedad: hay que investigar consumidores, distinguir alternativas plausibles, decidir el vocabulario y seleccionar los casos que protegen riesgos reales. Ahí puede aportar más un modelo con mayor capacidad de razonamiento y contexto. Cuando la API y los tests ya fijan el resultado observable, una implementación rutinaria puede delegarse a un modelo más simple que reciba feedback inmediato al ejecutar la suite.

Esta separación no es una garantía ni una obligación. Un algoritmo difícil o un cambio de alto riesgo puede necesitar el modelo más capaz también durante la implementación. Y el modelo que implementa no debe modificar los tests para conseguir el verde: si descubre que el contrato es imposible, incompleto o contradictorio, debe devolver esa evidencia a la fase de especificación.

La especificación tiene además un límite deliberado: solo exige los casos y garantías que contiene. Una suite verde no demuestra comportamientos que nunca fueron representados. Por eso el trabajo más importante ocurre antes de implementar: elegir la API, las diferencias observables, los límites y las casuísticas que merecen convertirse en evidencia ejecutable.

Un reparto de trabajo útil#

El reparto cambia con el riesgo. Las personas aportan intención, prioridades y decisiones de producto o arquitectura; la IA investiga el repositorio, propone casos, implementa y ejecuta verificaciones. Las decisiones ambiguas y el resultado observable se revisan en común.

Delegar una tarea no significa delegar su criterio de aceptación. Cuanto mayor sea el impacto de una decisión, más explícita debe quedar antes de convertirla en código. En cambios rutinarios, los tests y las convenciones del repositorio pueden proporcionar casi todo ese contexto.

Un flujo posible#

Este flujo es adaptable:

  1. Investiga consumidores, comportamiento actual y preguntas abiertas.
  2. Define la API y expresa cada diferencia observable importante en un test que falle por la razón esperada.
  3. Implementa sin reescribir la especificación ni ampliar el contrato.
  4. Ejecuta las verificaciones y revisa el diff como consumidor de la API.

El ciclo puede volver atrás. Un test difícil de escribir quizá revele una API incómoda; una implementación puede mostrar que faltaba representar un resultado. Corregir el contrato en ese momento es parte del diseño, no un fracaso del proceso.

Qué suele degradar el resultado#

El resultado se degrada cuando falta el consumidor o el comportamiento esperado, el contexto oculta las restricciones, la IA inventa decisiones o los tests fijan el mecanismo. También cuando se adapta la prueba al código generado o se da el cambio por terminado sin ejecutar las verificaciones reales.

La solución no es escribir un prompt enorme. Es entregar contexto seleccionado: contrato, casos, límites, convenciones y comandos de verificación. Los apéndices ofrecen plantillas breves para investigar y acordar el contrato y para implementar y verificarlo.

Ejemplo en Go#

El test describe el contrato de un búfer con capacidad uno. La implementación no está incluida a propósito: podría ser escrita por una persona o generada con IA y seguiría siendo evaluada por las mismas observaciones.

import "testing"

func TestBoundedBufferContract(t *testing.T) {
    buffer := NewBoundedBuffer[int](1)

    if got := buffer.Push(7); got != Stored {
        t.Fatalf("expected Stored, got %v", got)
    }
    if got := buffer.Push(8); got != Full {
        t.Fatalf("expected Full, got %v", got)
    }

    value, ok := buffer.Pop()
    if !ok || value != 7 {
        t.Fatalf("expected first value, got %v, %v", value, ok)
    }
}

El caso fija capacidad, respuesta y conservación del primer valor. No fija clases auxiliares, número de llamadas ni estructura interna. Esa libertad permite que la IA proponga una implementación y que el equipo la cambie después sin alterar el contrato.

Qué mejora en el código esperado#

Pensar en APIs y casos de prueba estrecha el espacio de soluciones sin elegir de antemano el mecanismo. La IA recibe nombres con significado, límites concretos y ejemplos ejecutables; el equipo recibe una forma objetiva de revisar el resultado.

El beneficio no es que todo código generado sea correcto. Es que deja de evaluarse solo por su apariencia: debe respetar el contrato, superar los casos acordados y conservar la libertad interna del módulo.

La IA acelera una propuesta; el contrato y las pruebas permiten decidir si esa propuesta sirve.