IBM entregó un sistema de control de satélites de la NASA con 0,1 defectos por mil líneas de código. El promedio de la industria en ese momento estaba entre 10 y 50. No lo lograron contratando ingenieros más inteligentes o trabajando más horas. Lo lograron prohibiendo a los desarrolladores ejecutar su propio código.
Esto era Cleanroom software engineering, desarrollado por Harlan Mills en IBM en la década de 1970. El nombre proviene de la fabricación de semiconductores, donde el objetivo es prevenir las condiciones que crean defectos. No pruebas la calidad hacia adentro. La diseñas hacia adentro.
El problema: el testing encuentra defectos, no los previene
La mayoría de la ingeniería de software asume un ciclo de escribir-probar-corregir. Escribes código, lo ejecutas, encuentras bugs, los arreglas. Se siente productivo. También está matemáticamente garantizado que deja defectos detrás.
El testing solo puede probar la presencia de bugs, nunca su ausencia. Dijkstra dijo esto en 1969. Si tu código tiene mil posibles caminos de ejecución y tu suite de pruebas cubre cincuenta de ellos, tienes 950 caminos no probados. La tasa de defectos promedio de la industria de 10-50 por KLOC no es un fracaso de rigor en el testing. Es el resultado predecible de un proceso que permite que existan defectos.
Cleanroom invierte esto. En lugar de escribir código y luego probarlo, escribes código que es correcto por construcción.
Cómo funciona Cleanroom en realidad
El método tiene tres prácticas rígidas que trabajan juntas.
Primero, desarrollo incremental bajo statistical process control. El proyecto se divide en pequeños incrementos, cada uno agregando un subconjunto bien definido de funcionalidad. Cada incremento pasa por especificación, diseño, verificación y testing como una unidad completa. La clave es que las tasas de defectos se miden por incremento. Si un incremento excede su objetivo de defectos, el proceso se detiene y el equipo averigua qué salió mal con el método, no con los individuos.
Segundo, diseño funcional-teórico usando box structures. Cada componente de software se define en tres niveles:
- Black box: El comportamiento externo, definido puramente como una función matemática de entradas a salidas. Sin state, sin detalle de implementación.
- State box: La función interna de transición de state. ¿Qué state mantiene el componente y cómo cambia?
- Clear box: La implementación real, construida a partir de componentes verificados.
Cada nivel se verifica contra el superior antes de continuar. No escribes la Clear box hasta que la State box esté probada correcta. No escribes la State box hasta que la Black box esté probada correcta. Esto suena lento. No lo es, porque no estás debuggeando. No pasas tardes en un debugger. El código funciona la primera vez que compila.
Tercero, y esta es la parte que explota cerebros: ningún execution testing por parte de los desarrolladores. Las personas que escriben el código no lo compilan, lo ejecutan ni hacen unit testing. El testing lo realiza un equipo separado usando métodos estadísticos. Tratan el software como una Black box y generan casos de prueba basados en usage profiles, no en la estructura del código.
Esta separación no es crueldad burocrática. Es el mecanismo central que fuerza la corrección por construcción. Si sabes que no puedes ejecutar tu código para “ver si funciona”, te ves obligado a pensar cada caso antes de escribirlo. No puedes confiar en que el compiler atrape tus errores, ni en un rápido python script.py para revelar el bug obvio. Tienes que estar en lo correcto.
Los números que importan
La IBM Federal Systems Division usó Cleanroom en múltiples proyectos en las décadas de 1980 y 1990. Los resultados fueron consistentes entre equipos, lenguajes y dominios de aplicación.
El sistema de control de satélites de la NASA: 0,1 defectos por KLOC en prueba final. Un sistema de facturación COBOL: 0,3 defectos por KLOC. Un sistema en tiempo real Ada: 0,4 defectos por KLOC. Compáralo con los promedios de la industria en ese momento: 10-50 defectos por KLOC en unit test, 5-10 aún presentes al momento de la entrega.
Los equipos de Cleanroom también entregaban más rápido. Los datos de la industria en ese momento mostraban que el 50-70 % del esfuerzo de software se destinaba a testing y debugging. Los equipos de Cleanroom dedicaban ese tiempo al diseño, y el código funcionaba a la primera.
Por qué casi nadie lo usa
Si Cleanroom es tan efectivo, ¿por qué no lo usa todo el mundo?
No puedes “simplemente probarlo” en un sprint. El método es interdependiente. El statistical process control solo funciona si mides cada incremento. Las box structures solo funcionan si haces la verificación formal. La regla de no ejecución solo funciona si es absoluta. La adopción parcial te da ninguno de los beneficios y todo el overhead.
El mercado también cambió. En las décadas de 1970 y 1980, el software se entregaba en cinta. Un bug en production era caro de arreglar. Hoy entregamos over the wire, y podemos parchear en minutos. El incentivo económico para software zero-defect se debilitó.
A la mayoría de nosotros también nos gusta ejecutar nuestro código. El feedback loop inmediato de escribir-ejecutar-corregir es gratificante. Cleanroom te pide que retrases esa gratificación hasta que hayas pensado el problema completamente. Para muchos desarrolladores, esto se siente como intentar escribir prosa sin poder releerla.
Qué puedes robar sin adoptar toda la religión
Probablemente no puedas implementar Cleanroom completo en tu empresa. Tu manager te miraría raro. Pero puedes adoptar partes de él y obtener beneficios reales.
Escribe la interfaz antes de la implementación. Define entradas, salidas y preconditions antes de escribir el body. Declara lo que la función promete, no solo lo que hace.
# precondition: items is a non-empty list of comparable elements
# postcondition: returns the smallest element in items
# raises: ValueError if items is empty
def min_item(items: list) -> any:
if not items:
raise ValueError("items must not be empty")
smallest = items[0]
for item in items[1:]:
if item < smallest:
smallest = item
return smallest
Esta es una función trivial, pero la disciplina escala. Para un ejemplo más complejo, define una state machine explícitamente antes de implementarla.
from enum import Enum, auto
class ConnectionState(Enum):
DISCONNECTED = auto()
CONNECTING = auto()
CONNECTED = auto()
CLOSING = auto()
# Allowed transitions:
# DISCONNECTED -> CONNECTING (on connect())
# CONNECTING -> CONNECTED (on handshake complete)
# CONNECTING -> DISCONNECTED (on timeout/error)
# CONNECTED -> CLOSING (on close())
# CLOSING -> DISCONNECTED (on ack received)
# Any other transition is illegal and raises StateError
class StateMachine:
_transitions = {
ConnectionState.DISCONNECTED: {ConnectionState.CONNECTING},
ConnectionState.CONNECTING: {ConnectionState.CONNECTED, ConnectionState.DISCONNECTED},
ConnectionState.CONNECTED: {ConnectionState.CLOSING},
ConnectionState.CLOSING: {ConnectionState.DISCONNECTED},
}
def __init__(self):
self.state = ConnectionState.DISCONNECTED
def transition(self, new_state: ConnectionState) -> None:
if new_state not in self._transitions.get(self.state, set()):
raise StateError(f"Illegal transition: {self.state.name} -> {new_state.name}")
self.state = new_state
Al codificar las transiciones válidas explícitamente, haces que los estados ilegales sean unrepresentable. El código no puede entrar en un estado inválido porque la state machine lo impide. Esta es la filosofía de box structures en miniatura.
Haz que alguien más pruebe tu código. La separación de autoría y verificación es la idea más radical de Cleanroom y la más transferible. Cuando pruebas tu propio código, pruebas tus propias suposiciones. Alguien más probará el caso que no consideraste porque parecía “obvio” que no ocurriría.
Mide defect density por unidad de trabajo. Rastrea cuántos bugs escapan de cada fase de tu proceso. Si tu sprint consistentemente se entrega con integration bugs, el problema no son desarrolladores descuidados. El problema es que tu proceso permite que existan integration bugs. Arregla el proceso.
Donde esto se desmorona
Cleanroom no es una solución universal. Funciona mejor cuando los requirements son estables y la corrección importa más que el time-to-market. Funciona mal para el desarrollo exploratorio y el rapid prototyping, donde el objetivo es descubrir qué quieren los usuarios en lugar de implementar correctamente una especificación conocida.
También requiere management buy-in. No puedes hacer Cleanroom en secreto. El statistical process control requiere recolección de datos en todo el equipo. La regla de no ejecución requiere que la gerencia realmente la haga cumplir, incluso cuando se acercan las deadlines y los desarrolladores quieren “solo verificar rápidamente si esto funciona”.
La conclusión
Los 0,1 defectos por KLOC de IBM no fueron un milagro. Fueron el resultado de un proceso diseñado para prevenir defectos en lugar de detectarlos. Las prácticas específicas – box structures, statistical testing y aislamiento de desarrolladores – pueden parecer extrañas para la cultura de software moderna. Pero el principio subyacente es atemporal: es más barato pensar antes de escribir que debuggear después.
No necesitas adoptar Cleanroom al por mayor. Empieza con una función. Escribe su contract antes de su body. Haz que un colega revise la lógica antes de ejecutarla. Rastrea de dónde vienen tus bugs y arregla el proceso que los produce. Zero defects por KLOC probablemente no sea tu objetivo. Pero pasar de 50 a 5 es alcanzable, y el método es el mismo. Piensa primero. Escribe segundo. Ejecuta al final.