La ingeniería de software Cleanroom entrega 0.1 defectos por mil líneas de código. El promedio de la industria es de 10 a 50. El problema es que Cleanroom completo requiere que dividas tu equipo en autores y verificadores, escribas especificaciones formales antes de cada module, y prohíbas a los desarrolladores ejecutar su propio código hasta que un equipo de pruebas separado lo haya validado estadísticamente.

La mayoría de los managers de ingeniería miran ese proceso, hacen una rápida cuenta de headcount, y deciden que la calidad no vale el overhead. Tienen medio razón. El proceso completo de Cleanroom es pesado. Pero los principios subyacentes son livianos, y puedes adoptarlos sin contratar un ejército de verificación o prohibir cargo test.

Qué es Cleanroom en Realidad

Cleanroom es un proceso de desarrollo de software desarrollado en IBM en los años 70 por Harlan Mills. El nombre viene de la fabricación de semiconductores: previenes las condiciones que crean defectos, en lugar de inspeccionarlos después. La afirmación central es que el software puede ser correcto por construcción si lo diseñas con suficiente cuidado como para que los bugs sean imposibles antes de que el código se escriba.

El método se basa en tres prácticas: desarrollo incremental bajo control estadístico de procesos, diseño funcional-teórico con estructuras de caja, y pruebas estadísticas de uso realizadas por un equipo separado. Estas tres prácticas son interdependientes. Rompes una, y las otras pierden poder.

Esa interdependencia es de donde viene el mito del overhead. Los equipos asumen que deben adoptar las tres o nada. Eso no es verdad. Las prácticas se refuerzan mutuamente, pero cada una produce valor por sí sola.

Dónde Vive el Overhead en Realidad

El 80% de overhead que la gente teme viene de dos requisitos específicos.

Primero, la regla de no ejecución. En Cleanroom completo, el desarrollador que escribe el código no lo compila, ejecuta, ni hace unit tests. Un equipo de verificación separado maneja toda la ejecución. Esto fuerza a los desarrolladores a razonar cada caso antes de tipear, que es exactamente por qué el código funciona a la primera. También requiere que dupliques tu headcount de ingeniería o dividas tu equipo existente en dos grupos que se resentirán mutuamente.

Segundo, el paso de verificación formal. Antes de escribir la implementación de caja clara, el equipo escribe una especificación de caja negra y un refinamiento de caja de estado, y demuestra a mano que la caja de estado es equivalente a la caja negra. Esto es demostración con lápiz y papel, no un asistente de pruebas. Funciona, pero toma tiempo y entrenamiento que la mayoría de los equipos no tienen.

La tercera práctica, desarrollo incremental con control estadístico de procesos, es de hecho gratis si ya estás haciendo sprints. Entregas incrementos pequeños, mides la densidad de defectos por incremento, y detienes el proceso cuando un incremento excede su objetivo de defectos para averiguar qué salió mal con el método. Esto son simplemente retrospectivas basadas en datos con una definición más estricta de “done.”

El Subconjunto Pragmático: Mantén la Estructura, Elimina la Burocracia

Puedes obtener la mayor parte de la reducción de defectos de Cleanroom manteniendo la disciplina estructural y descartando los mandatos organizacionales.

Esto es lo que mantener.

Escribe el contrato antes del código. No una especificación formal en notación Z. Solo una declaración clara de inputs, outputs, precondiciones y postcondiciones. Si no puedes escribir qué significa correcto, no puedes escribir código correcto.

Codifica las state machines explícitamente. La mayoría de los bugs viven en transiciones de estado que el desarrollador asumió imposibles. Define tus estados y transiciones en una tabla o estructura de datos antes de escribir la lógica.

Haz que alguien más pruebe tu lógica. La separación de autoría y verificación es la idea más poderosa de Cleanroom. No necesitas un equipo separado. Necesitas una persona que no escribió el código para diseñar los casos de prueba. Cuando pruebas tu propio código, pruebas tus propias suposiciones.

Mide la densidad de defectos por incremento. Rastrea cuántos bugs escapan de cada fase. Si los bugs de integración siguen filtrándose, el problema no son desarrolladores descuidados. El problema es que tu proceso permite que existan bugs de integración.

Esto es lo que eliminar.

Elimina la regla de no ejecución. Deja que los desarrolladores ejecuten su propio código. La disciplina de razonar primero es valiosa incluso si te permites una rápida verificación de cordura después. El punto es pensar antes de correr el compiler, no pretender que el compiler no existe.

Elimina la demostración formal a mano. A menos que estés escribiendo software de aviónica, la demostración con lápiz y papel es exceso. Reemplázala con property-based tests y diseño guiado por tipos. Estas son versiones mecanizadas del mismo razonamiento, y corren en CI.

Elimina el requisito de pruebas estadísticas de uso. Cleanroom completo prueba contra perfiles de uso, no contra code coverage. Eso es excelente si tienes los datos. Si no, property-based testing y mutation testing te dan casi la misma confianza con herramientas que ya usas.

Un Workflow Liviano de Cleanroom en Python

Así se ve en la práctica para un solo module. Empieza con el contrato.

"""
Black Box: Token Bucket Rate Limiter

Stimuli: request(n), add_tokens(k)
Precondition: n > 0, k >= 0, capacity > 0
Postcondition:
  - request(n) grants iff available tokens >= n
  - request(n) reduces available tokens by n if granted
  - add_tokens(k) increases available tokens by k, capped at capacity
  - available tokens never negative, never exceeds capacity
"""

Luego codifica la state machine.

from enum import Enum, auto

class RateLimitState(Enum):
    READY = auto()      # tokens >= 1, requests may grant
    DEPLETED = auto()   # tokens == 0, requests deny

# Transitions depend on token count, not external events
# READY -> DEPLETED when tokens reach 0
# DEPLETED -> READY when tokens added above 0

Luego escribe la implementación.

import time
from dataclasses import dataclass

@dataclass
class TokenBucket:
    capacity: int
    tokens: int
    refill_rate: float
    last_refill: float

    def _refill(self) -> None:
        now = time.monotonic()
        elapsed = now - self.last_refill
        added = int(elapsed * self.refill_rate)
        if added > 0:
            self.tokens = min(self.capacity, self.tokens + added)
            self.last_refill = now

    def request(self, n: int) -> bool:
        if n <= 0:
            raise ValueError("request must be positive")
        self._refill()
        if self.tokens >= n:
            self.tokens -= n
            return True
        return False

    def add_tokens(self, k: int) -> None:
        if k < 0:
            raise ValueError("cannot add negative tokens")
        self.tokens = min(self.capacity, self.tokens + k)

El contrato vive en el docstring. La state machine es explícita. La implementación es corta y verificable por inspección. Esto no es Cleanroom completo. Tampoco es cowboy coding.

El Paso de Verificación que Realmente Puedes Hacer

En Cleanroom completo, un equipo separado escribe pruebas estadísticas basadas en perfiles de uso. En la versión pragmática, escribes property-based tests y haces que un colega los revise.

from hypothesis import given, strategies as st

@given(
    capacity=st.integers(min_value=1, max_value=1000),
    initial=st.integers(min_value=0, max_value=1000),
    requests=st.lists(st.integers(min_value=1, max_value=100), max_size=50),
)
def test_token_bucket_never_overdrafts(capacity, initial, requests):
    bucket = TokenBucket(
        capacity=capacity,
        tokens=min(initial, capacity),
        refill_rate=0.0,
        last_refill=time.monotonic(),
    )

    for n in requests:
        granted = bucket.request(n)
        assert bucket.tokens >= 0
        if granted:
            # tokens were deducted, so pre-request balance was sufficient
            pass
        else:
            # request denied, current tokens insufficient
            assert bucket.tokens < n

Esta prueba no verifica outputs específicos. Verifica una invariante: el bucket nunca concede más tokens de los que tiene. Esa es la versión property-based de una prueba de Cleanroom. Corre automáticamente, encuentra edge cases que no se te ocurrieron, y no requiere un equipo de verificación separado.

Los Trade-Offs Son Reales

Cleanroom pragmático no es gratis. Escribir contratos antes del código toma disciplina. Las state machines explícitas se sienten como boilerplate cuando el código es “obvio.” Hacer que alguien más pruebe tu lógica requiere coordinación.

También es menos poderoso que la cosa real. La regla de no ejecución de Cleanroom completo fuerza una profundidad de razonamiento que no puedes replicar cuando el REPL está a un keystroke de distancia. La prueba formal atrapa errores lógicos que los property-based tests podrían omitir si tus properties están mal.

Pero la comparación no es Cleanroom pragmático contra Cleanroom completo. La comparación es Cleanroom pragmático contra lo que estás haciendo ahora. Si tu proceso actual entrega 20 defectos por KLOC y Cleanroom pragmático te lleva a 5, eso es una mejora de 4× por una fracción del overhead.

Cuándo Vale la Pena

No uses esto para cada función de utilidad. Úsalo para código donde un bug es costoso: autorización, facturación, protocols distribuidos, state machines con más de tres estados, y cualquier cosa que haya causado un incident en producción dos veces.

La señal de que necesitas esto no es complejidad. Es sorpresa repetida. Si tu equipo sigue encontrando la misma categoría de bug en pruebas o producción, el problema no es que los desarrolladores son descuidados. El problema es que el contrato del código nunca fue definido, así que “correcto” nunca fue especificado.

Empieza con un Module

No necesitas aprobación de management o un overhaul de procesos. Elige un module que te haya mordido antes. Escribe su contrato en un docstring antes de tocar la implementación. Define los estados y transiciones. Pídele a un colega que escriba pruebas sin mirar tu código. Corre property-based tests en CI.

Eso es todo. Ningún equipo separado. Ninguna notación formal. Ninguna prohibición de ejecutar tu propio código.

Los 0.1 defectos por KLOC de Cleanroom no eran magia. Eran el output de un proceso que forzaba a la gente a definir correcto antes de construirlo. Puedes obtener la mayor parte de ese effect con un docstring, una tabla de state machine, y un colega que pruebe tus suposiciones en lugar de tu código.