IBM lieferte ein NASA-Satellitenkontrollsystem mit 0,1 Defects pro tausend Zeilen Code aus. Der Industriedurchschnitt zu dieser Zeit lag zwischen 10 und 50. Das wurde nicht erreicht, indem klügere Ingenieure eingestellt oder länger gearbeitet wurde. Es wurde erreicht, indem Entwicklern verboten wurde, ihren eigenen Code auszuführen.

Das war Cleanroom Software Engineering, entwickelt von Harlan Mills bei IBM in den 1970ern. Der Name stammt aus der Halbleiterfertigung, wo das Ziel darin besteht, die Bedingungen zu verhindern, die Defects erzeugen. Du testest Qualität nicht hinein. Du designst sie hinein.

Das Problem: Testing findet Defects, es verhindert sie nicht

Die meiste Softwareentwicklung geht von einer Write-Test-Fix-Schleife aus. Du schreibst Code, du führst ihn aus, du findest Bugs, du behebst sie. Das fühlt sich produktiv an. Es ist auch mathematisch garantiert, dass dabei Defects zurückbleiben.

Testing kann nur die Anwesenheit von Bugs beweisen, nie ihre Abwesenheit. Dijkstra sagte das 1969. Wenn dein Code tausend mögliche Ausführungspfade hat und deine Test-Suite fünfzig davon abdeckt, hast du 950 ungetestete Pfade. Die durchschnittliche Industrie-Defect-Rate von 10-50 pro KLOC ist kein Versagen von Testing-Rigor. Sie ist die vorhersehbare Ausgabe eines Prozesses, der Defects existieren lässt.

Cleanroom kehrt das um. Statt Code zu schreiben und ihn dann zu testen, schreibst du Code, der konstruktionsbedingt korrekt ist.

Wie Cleanroom tatsächlich funktioniert

Die Methode hat drei starre Praktiken, die zusammenwirken.

Erstens: Inkrementelle Entwicklung unter statistical process control. Das Projekt wird in kleine Inkremente aufgeteilt, die jeweils eine wohldefinierte Teilmenge an Funktionalität hinzufügen. Jedes Inkrement durchläuft Spezifikation, Design, Verifikation und Testing als komplette Einheit. Der Schlüssel ist, dass Defect-Raten pro Inkrement gemessen werden. Wenn ein Inkrement sein Defect-Ziel überschreitet, wird der Prozess gestoppt und das Team findet heraus, was mit der Methode schiefgelaufen ist – nicht mit den Einzelpersonen.

Zweitens: Funktionstheoretisches Design mit box structures. Jede Software-Komponente wird auf drei Ebenen definiert:

  • Black box: Das externe Verhalten, definiert rein als mathematische Funktion von Inputs zu Outputs. Kein State, kein Implementierungsdetail.
  • State box: Die interne State-Transition-Funktion. Welchen State hält die Komponente, und wie ändert er sich?
  • Clear box: Die eigentliche Implementierung, aufgebaut aus verifizierten Komponenten.

Jede Ebene wird gegen die darüberliegende verifiziert, bevor fortgefahren wird. Du schreibst die Clear box nicht, bis die State box als korrekt bewiesen ist. Du schreibst die State box nicht, bis die Black box als korrekt bewiesen ist. Das klingt langsam. Ist es nicht, denn du debuggst nicht. Du verbringst keine Nachmittage im Debugger. Der Code funktioniert beim ersten Kompilieren.

Drittens – und das ist der Teil, der Hirne bricht: Kein Execution-Testing durch Entwickler. Die Leute, die den Code schreiben, kompilieren, führen oder unit-testen ihn nicht. Testing wird von einem separaten Team mit statistischen Methoden durchgeführt. Sie behandeln die Software als Black box und generieren Testfälle basierend auf Usage-Profilen, nicht auf der Code-Struktur.

Diese Trennung ist keine bürokratische Grausamkeit. Sie ist der Kernmechanismus, der Konstruktionsbedingte Korrektheit erzwingt. Wenn du weißt, dass du deinen Code nicht ausführen kannst, um „zu sehen, ob er funktioniert“, bist du gezwungen, jeden Fall durchzudenken, bevor du ihn eintippst. Du kannst dich nicht darauf verlassen, dass der Compiler deine Fehler findet, oder auf ein schnelles python script.py, um den offensichtlichen Bug zu enthüllen. Du musst richtig liegen.

Die Zahlen, die zählen

Die IBM Federal Systems Division nutzte Cleanroom in den 1980er und 1990er Jahren in mehreren Projekten. Die Ergebnisse waren über Teams, Sprachen und Anwendungsdomänen hinweg konsistent.

Das NASA-Satellitenkontrollsystem: 0,1 Defects pro KLOC im Final Test. Ein COBOL-Billing-System: 0,3 Defects pro KLOC. Ein Ada-Realtime-System: 0,4 Defects pro KLOC. Zum Vergleich die Industriedurchschnitte zu der Zeit: 10-50 Defects pro KLOC beim Unit Test, 5-10 verbleiben noch bei der Auslieferung.

Cleanroom-Teams lieferten auch schneller. Industriedaten zu der Zeit zeigten, dass 50-70 % des Software-Aufwands für Testing und Debugging ging. Cleanroom-Teams investierten diese Zeit stattdessen in Design, und der Code funktionierte beim ersten Mal.

Warum fast niemand es nutzt

Wenn Cleanroom so effektiv ist, warum macht es dann nicht jeder?

Du kannst es nicht „einfach mal ausprobieren“ in einem Sprint. Die Methode ist voneinander abhängig. Statistical process control funktioniert nur, wenn du jedes Inkrement misst. Box structures funktionieren nur, wenn du die formale Verifikation durchführst. Die No-Execution-Regel funktioniert nur, wenn sie absolut ist. Partielle Übernahme bringt dir keine der Vorteile, aber den ganzen Overhead.

Der Markt hat sich auch verändert. In den 1970ern und 1980ern wurde Software auf Band ausgeliefert. Ein Bug in Production war teuer zu beheben. Heute liefern wir Over-the-Wire aus, und wir können in Minuten patchen. Der ökonomische Anreiz für Zero-Defect-Software hat nachgelassen.

Die meisten von uns mögen es auch, ihren Code laufen zu lassen. Die unmittelbare Feedback-Schleife von Schreiben-Ausführen-Fixen ist befriedigend. Cleanroom verlangt von dir, diese Befriedigung hinauszuzögern, bis du das Problem komplett durchdacht hast. Für viele Entwickler fühlt sich das an, als würde man versuchen, Prosa zu schreiben, ohne sie zurücklesen zu dürfen.

Was du klauen kannst, ohne die ganze Religion zu übernehmen

Du kannst vermutlich nicht das volle Cleanroom in deinem Unternehmen implementieren. Dein Manager würde dich komisch anschauen. Aber du kannst Teile davon übernehmen und echte Vorteile erzielen.

Schreibe das Interface vor der Implementierung. Definiere Inputs, Outputs und Preconditions, bevor du den Body schreibst. Formuliere, was die Funktion verspricht, nicht nur, was sie tut.

# 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

Das ist eine triviale Funktion, aber die Disziplin skaliert. Für ein komplexeres Beispiel definiere eine State Machine explizit, bevor du sie implementierst.

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

Indem du gültige Transitionen explizit kodierst, machst du illegale Zustände unrepresentable. Der Code kann keinen invaliden Zustand erreichen, weil die State Machine ihn verhindert. Das ist die Box-Structure-Philosophie in Miniatur.

Lass jemand anderen deinen Code testen. Die Trennung von Autorschaft und Verifikation ist Cleanrooms radikalste und gleichzeitig übertragbarste Idee. Wenn du deinen eigenen Code testest, testest du deine eigenen Annahmen. Jemand anderes wird den Fall ausprobieren, den du nicht bedacht hast, weil es „offensichtlich“ schien, dass er nicht passieren würde.

Miss Defect-Density pro Arbeitseinheit. Verfolge, wie viele Bugs aus jeder Phase deines Prozesses entkommen. Wenn dein Sprint konsistent mit Integration-Bugs ausgeliefert wird, ist das Problem nicht nachlässige Entwickler. Das Problem ist, dass dein Prozess Integration-Bugs existieren lässt. Behebe den Prozess.

Wo das auseinanderfällt

Cleanroom ist keine universelle Lösung. Es funktioniert am besten, wenn Requirements stabil sind und Korrektheit wichtiger ist als Time-to-Market. Es funktioniert schlecht für explorative Entwicklung und Rapid Prototyping, wo das Ziel darin besteht, herauszufinden, was die Nutzer wollen, anstatt eine bekannte Spezifikation korrekt zu implementieren.

Es erfordert auch Management-Buy-in. Du kannst Cleanroom nicht im Geheimen machen. Der statistical process control erfordert Datenerhebung über das gesamte Team. Die No-Execution-Regel erfordert, dass das Management sie tatsächlich durchsetzt, auch wenn Deadlines drohen und Entwickler „nur schnell checken wollen, ob das funktioniert“.

Das Fazit

IBMs 0,1 Defects pro KLOC waren kein Wunder. Es war die Ausgabe eines Prozesses, der darauf ausgelegt war, Defects zu verhindern, anstatt sie zu erkennen. Die spezifischen Praktiken – Box Structures, statistical testing und Entwickler-Isolation – können für moderne Softwarekultur fremd wirken. Aber das zugrunde liegende Prinzip ist zeitlos: Es ist billiger, vor dem Tippen nachzudenken, als danach zu debuggen.

Du musst Cleanroom nicht im Ganzen übernehmen. Starte mit einer Funktion. Schreibe ihren Contract vor ihrem Body. Lass einen Kollegen die Logik reviewen, bevor du sie ausführst. Verfolge, wo deine Bugs herkommen, und behebe den Prozess, der sie erzeugt. Zero Defects pro KLOC ist vermutlich nicht dein Ziel. Aber von 50 auf 5 zu kommen, ist erreichbar, und die Methode ist dieselbe. Denk erst. Tippe zweit. Führe zuletzt aus.