La mayoría de los desarrolladores usan los LLMs al revés. Describimos lo que queremos en cinco palabras, recibimos 200 líneas de código y pasamos la siguiente hora depurando suposiciones que el modelo hizo.

Pasé tres meses ejecutando el flujo de trabajo opuesto: escribir una explicación de diseño completa antes de pedir una sola línea de código. El código resultante tenía menos bugs, pero la verdadera mejora fue en mi propia comprensión.

¿Qué es la programación literaria con LLMs?

Donald Knuth acuñó la “programación literaria” en 1984. La idea era simple: escribir prosa que explique tu programa, luego tejer código en esa prosa. La mayoría de los desarrolladores la ignoraron porque mantener dos artefactos era tedioso. Los LLMs cambian ese cálculo. La prosa ya no es documentación. Es el prompt.

Cuando explicas tu enfoque a un LLM antes de pedir código, estás haciendo algo diferente del prompt engineering estándar. No estás optimizando tokens para el modelo. Te estás obligando a articular constraints, edge cases y dependencias antes de que el modelo alucine una arquitectura para ti. El LLM se convierte en un patito de goma que responde, pero solo después de que has hecho el pensamiento real.

El experimento: 30 días de explicar primero

Elegí 12 tareas reales de mi backlog, desde un parser CSV hasta una estrategia de reconexión WebSocket. Para cada una, usé dos enfoques:

  1. El atajo: Pegar la descripción del ticket en el LLM y pedir código.
  2. La explicación: Escribir de 300 a 500 palabras describiendo el problema, los constraints, la estrategia de manejo de errores y los casos de prueba. Luego pedirle al LLM que implemente ese diseño.

Evalué la salida en tres criterios: bugs encontrados en code review, tiempo hasta merge y número de prompts de tracing necesarios para corregir problemas.

Los números fueron desproporcionados. El código con explicación primero requirió un promedio de 1.2 prompts de tracing, comparado con 4.7 para el enfoque de atajo. La code review encontró 0.3 bugs por envío con explicación primero versus 2.1 bugs para el código de atajo.

La trampa era el tiempo. Escribir la explicación agregó de 15 a 20 minutos al inicio de cada tarea. Si eso vale la pena depende de lo que estés construyendo.

Cómo estructurar una explicación que realmente funcione

Una buena explicación no es solo un prompt más largo. Es un documento de diseño con una estructura específica. Aquí está la plantilla que refiné después de la primera semana:

## Problem
[What are we solving and why?]

## Constraints
[What can't we do? What formats, protocols, or dependencies must we respect?]

## Edge Cases
[What happens when input is null, empty, malformed, or hostile?]

## Error Handling Strategy
[Fail fast or fail graceful? What do we log? What do we return?]

## Test Cases
[The specific inputs and expected outputs that prove this works.]

Aquí hay un ejemplo real. Necesitaba una función que valide una firma de webhook usando HMAC-SHA256, pero con un giro: el header de firma podía usar dos formatos diferentes dependiendo de qué servicio enviara la solicitud.

Problem: Validar firmas de webhook entrantes. Algunos remitentes usan X-Signature: sha256=<hex> y otros usan X-Signature: <hex> sin el prefijo. Necesitamos soportar ambos sin romper integraciones existentes.

Constraints: Debe usar el module crypto de Node.js. Debe usar comparación de tiempo constante para prevenir ataques de timing. Debe devolver un booleano claro, nunca lanzar con input malformed.

Edge Cases: El header falta. El header es string vacío. El header tiene un prefijo desconocido como sha512=. El string hex tiene longitud impar o contiene caracteres no hex.

Error Handling Strategy: Registrar la razón específica de falla a nivel debug, pero devolver false para todos los casos inválidos. Nunca filtrar detalles de firma en mensajes de error.

Test Cases:

  1. sha256=abc123... válida coincide con la firma computada.
  2. abc123... desnuda válida coincide con la firma computada.
  3. Header faltante devuelve false.
  4. Firma incorrecta devuelve false.
  5. Caracteres no hex en la firma devuelven false.

Luego agregué: “Implementa esta función en TypeScript.”

El modelo devolvió esto:

import { createHmac, timingSafeEqual } from 'crypto';

export function validateWebhookSignature(
  payload: string,
  header: string | undefined,
  secret: string
): boolean {
  if (!header || header.length === 0) {
    return false;
  }

  const hexPattern = /^[0-9a-fA-F]+$/;
  let signatureHex: string;

  const prefixMatch = header.match(/^sha256=(.+)$/);
  if (prefixMatch) {
    signatureHex = prefixMatch[1];
  } else {
    signatureHex = header;
  }

  if (!hexPattern.test(signatureHex) || signatureHex.length % 2 !== 0) {
    return false;
  }

  const expected = createHmac('sha256', secret).update(payload).digest();
  const actual = Buffer.from(signatureHex, 'hex');

  if (actual.length !== expected.length) {
    return false;
  }

  return timingSafeEqual(actual, expected);
}

Esto compiló a la primera. Más importante aún, manejó cada edge case que especifiqué porque ya había hecho el pensamiento. El modelo no necesitó adivinar qué quería decir con “validar.”

Dónde ayuda y dónde desperdicia tiempo

Este enfoque brilla para cualquier cosa con lógica de ramificación, dependencias externas o implicaciones de seguridad. El validador de webhook de arriba es un ejemplo perfecto. El modelo no puede conocer tu modelo de amenazas a menos que se lo digas.

Pero para boilerplate, es excesivo. No escribo 500 palabras para generar un componente de formulario React o una migration de base de datos estándar. Para esos, el enfoque de atajo funciona bien porque los patrones están bien representados en los datos de entrenamiento y los riesgos son bajos.

La verdadera trampa es el terreno medio: tareas que parecen simples pero no lo son. Un parser CSV parece sencillo hasta que encuentras campos entre comillas que contienen comas, o marcadores BOM, o terminaciones de línea mixtas. Estas son las tareas donde omitir la explicación te cuesta más tiempo después.

Un flujo de trabajo que puedes robar hoy

No necesitas cambiar todo tu proceso. Prueba esto para una tarea esta semana:

  1. Abre tu chat de LLM y escribe la explicación primero. No menciones código todavía.
  2. Lee tu explicación de vuelta. Si no puedes articular los edge cases, detente. No estás listo para escribir código todavía, y definitivamente no estás listo para que un LLM lo escriba por ti.
  3. Agrega tu solicitud de implementación al mismo mensaje.
  4. Revisa la salida contra tus propios casos de prueba, no contra tu intuición.

El paso que la mayoría de la gente omite es el número dos. Si tu explicación es vaga, el LLM reflejará esa vaguedad de vuelta a ti en forma de bugs sutiles. La explicación es una smoke test para tu propia comprensión.

El verdadero beneficio no es el código

Después de 30 días, noté algo inesperado. El código era mejor, pero mis propias habilidades de diseño mejoraron más rápido. Escribir explicaciones te obliga a nombrar tus suposiciones explícitamente. Cuando haces eso, a menudo te das cuenta de que tu enfoque es incorrecto antes de escribir una sola línea de código.

El LLM sigue haciendo la escritura. Pero tú estás haciendo el pensamiento, que siempre fue el cuello de botella.

Si quieres probar esto tú mismo, empieza con tu próxima corrección de bug. Antes de pedir ayuda a un LLM, escribe dos párrafos explicando lo que crees que está pasando y por qué. Podrías resolverlo antes de terminar de escribir.