La mejor idea que nunca se adoptó

En 1984, Donald Knuth publicó un artículo que proponía una inversión radical. Los programas no deberían escribirse para compilers y anotarse para humanos. Deberían escribirse como literatura para humanos, de la cual los compilers extraen las partes ejecutables. Lo llamó literate programming, y construyó TeX de esta manera.

La idea es hermosa. También está casi completamente ausente del desarrollo de software moderno.

TeX sigue siendo uno de los programas grandes más fiables y mejor comprendidos jamás escritos. Ese éxito se debe en parte a que Knuth lo escribió como un literate program. Así que la pregunta no es si el literate programming puede funcionar. Claramente puede. La pregunta es por qué funcionó para Knuth y para casi nadie más.

Qué significa realmente la programación literaria

El sistema WEB de Knuth, más tarde CWEB para C, funciona dividiendo un solo archivo fuente en dos salidas diferentes. Escribes un documento narrativo que explica qué hace el programa, por qué lo hace y cómo encajan las piezas. Incrustados dentro de esa narrativa hay fragmentos de código.

La herramienta tangle extrae los fragmentos de código, los ordena para el compiler y produce un archivo que la máquina puede ejecutar. La herramienta weave extrae la prosa, formatea los fragmentos de código como bloques bien presentados y produce un documento que un humano puede leer.

Así es como se ve un pequeño programa en CWEB en la práctica:

@* Introduction. This program computes the sum of a list of integers.
The algorithm is straightforward: iterate through the array and accumulate.

@c
#include <stdio.h>

@<Function prototypes@>;
@<Main program@>;

@*2 The core logic.
We define |sum_array| to take a pointer and a length, returning the total.

@<Function definitions@>=
int sum_array(const int *arr, size_t n) {
    int total = 0;
    for (size_t i = 0; i < n; i++) {
        total += arr[i];
    }
    return total;
}

@*2 Entry point.
A small driver to exercise the function.

@<Main program@>=
int main(void) {
    int data[] = {1, 2, 3, 4, 5};
    size_t len = sizeof(data) / sizeof(data[0]);
    printf("%d\n", sum_array(data, len));
    return 0;
}

Observa lo que está sucediendo. El código no está organizado por orden de compilación. Está organizado por lógica narrativa. La explicación viene primero. La implementación sigue cuando el lector está listo para ella. La sintaxis @<Main program@> es un fragmento con nombre que puede definirse en cualquier parte del documento y ser ensamblado por tangle en el orden correcto para el compiler.

Este es el supuesto central: el humano lee una explicación de arriba hacia abajo. El compiler obtiene un grafo de dependencias de abajo hacia arriba. Ambos reciben el formato que prefieren.

Por qué funcionó para Knuth

Knuth no es un programador típico. Es un matemático y escritor que escribe programas. Diseña algoritmos en papel durante meses antes de tocar un teclado. Escribe libros que se leen décadas después. Cuando Knuth dice que el código debería ser literatura, se refiere a literatura real, y tiene la habilidad para lograrlo.

TeX también fue un candidato perfecto. Es un programa batch con una especificación estable. El núcleo algorítmico, especialmente las rutinas de salto de línea y salto de página, se beneficia enormemente de explicaciones matemáticas extendidas. El código cambia lentamente. La documentación sobrevive a la implementación.

La mayoría del software no es así. La mayoría del software cambia a diario. La especificación se descubre, no se diseña. La audiencia no es un lector que intenta entender un algoritmo. Es un desarrollador que intenta arreglar un bug antes de la reunión diaria.

El problema de la edición

La programación literaria asume un proceso de escritura. La mayoría de la programación es un proceso de edición.

Cuando escribes un ensayo, redactas, revisas y pules. La estructura está planificada. Cuando escribes software, exploras, pruebas, refactorizas y entregas. La estructura emerge. La documentación en un literate program no es una capa que aplicas después. Es la estructura primaria. Cada vez que renombras una variable, extraes una función o reordenas la lógica, estás reescribiendo la literatura.

Esto crea un ciclo vicioso. Si la prosa está fuertemente acoplada al código, el refactoring se vuelve costoso. Si la prosa está débilmente acoplada, se desvía del código y se vuelve incorrecta. De cualquier manera, la documentación se deteriora.

El control de versiones moderno empeora esto. Un literate program es un documento narrativo único. Un pull request es un diff contra un árbol de fuentes. Las herramientas de revisión de código entienden líneas en main.c. No entienden fragmentos narrativos en main.w. El ecosistema de herramientas, desde el resaltado de sintaxis hasta el análisis estático y los pipelines de CI, espera archivos fuente compilables. La programación literaria requiere que te excluyas de todo ello.

La falta de habilidades

Knuth asumió que los programadores también son escritores. La mayoría no lo son.

La buena escritura técnica es rara porque es difícil. Requiere empatía por un lector que aún no entiende lo que tú entiendes. Requiere la disciplina de explicar por qué, no solo qué. Requiere edición, que es una habilidad separada de la programación.

Cuando le pides a un equipo que escriba literate programs, le estás pidiendo que sea Knuth. Le estás pidiendo prosa extendida que explique la intención, explore alternativas y guíe a un lector a través del razonamiento. El comentario de código medio es // TODO: fix this. La brecha entre eso y la literatura no es un problema de herramientas. Es un problema humano.

Por eso los intentos de revivir la programación literaria con mejores herramientas han fracasado consistentemente. El cuello de botella nunca fue la toolchain de weave y tangle. El cuello de botella es que la mayoría de los programadores no quieren escribir ensayos, y la mayoría de las bases de código no recompensan la documentación de calidad de ensayo.

Qué realmente lo reemplazó

La industria no abandonó el objetivo de Knuth. Logró una versión más débil de él a través de medios diferentes.

Los sistemas de tipos ahora codifican intención que antes requería párrafos de explicación. Cuando una función acepta NonEmptyList<T> en lugar de List<T>, el type checker impone una garantía que la documentación solo podía describir. Cuando el borrow checker de Rust rechaza un escape de referencia, comunica una restricción que tomaría páginas explicar en prosa.

Los docstrings y las herramientas de documentación de API como Javadoc, Rustdoc y TypeDoc crearon un término medio. Mantienen la documentación adyacente al código sin requerir que impulse la estructura. Puedes leer el código fuente o la documentación generada, y ambos permanecen sincronizados porque viven en el mismo archivo.

Los test suites se convirtieron en documentación ejecutable. Una prueba bien escrita dice “dada esta entrada, espera esta salida” con más precisión de la que la prosa jamás podría. Las pruebas property-based codifican invariantes. Las pruebas snapshot capturan intención. Las pruebas se ejecutan en cada commit, así que no pueden desviarse como lo hace la prosa.

Quizás lo más importante es que los lenguajes de programación mismos se volvieron más literarios. Python se lee como pseudocódigo. La sintaxis if let de Rust expresa la intención del pattern matching directamente. Cuando el código es la explicación, no necesitas una explicación separada del código.

La versión moderna que sí funcionó

Hay un lugar donde la visión de Knuth sobrevivió casi intacta: los computational notebooks.

Jupyter notebooks, R Markdown y Observable permiten que la prosa y el código coexistan en un solo documento. La prosa explica. El código ejecuta. La salida aparece inline. Esto es literate programming en todo excepto en el nombre.

Los notebooks tuvieron éxito donde WEB falló porque el contexto es diferente. Un data scientist que explora un conjunto de datos está comprometido en un proceso narrativo. El código cambia lentamente. La audiencia es un lector humano que intenta entender una decisión. El ciclo de edición es exploración, no refactoring. Las restricciones que mataron la programación literaria en la ingeniería de software son características en la ciencia de datos.

La lección no es que la programación literaria estuviera equivocada. Es que la idea tiene un nicho ecológico estrecho. Prospera cuando el programa es un artefacto terminado destinado a ser estudiado. Se asfixia cuando el programa es un sistema vivo destinado a ser cambiado.

Qué sacar de esto

Probablemente no vas a adoptar CWEB para tu próximo servicio. Está bien. Pero la idea subyacente de Knuth todavía vale la pena aplicarla.

Nombra las cosas para que se expliquen solas. Una función llamada process_data es un fracaso de la programación literaria incluso si nunca tocas WEB. Una función llamada remove_expired_sessions_older_than lleva su propia documentación.

Escribe la explicación que desearías haber tenido. Si una sección de código requiere un párrafo de prosa para entenderse, escribe el párrafo. Ponlo en un comentario, un docstring o un documento de diseño. El medio es menos importante que el acto de explicar.

Separa la estructura narrativa de la estructura del compiler cuando ayude. El desarrollo guiado por README, los architecture decision records y los RFCs son formas de escribir la historia orientada al humano sin pelear contra tu toolchain.

Knuth quería que escribiéramos programas como libros. Terminamos escribiendo libros sobre programas. Esa es una victoria más débil de la que él esperaba, pero sigue siendo una victoria.