IBM a livré un système de contrôle de satellite de la NASA avec 0,1 défaut par mille lignes de code. La moyenne industrielle à l’époque se situait entre 10 et 50. Ils n’ont pas réussi cela en recrutant des ingénieurs plus intelligents ou en travaillant plus longtemps. Ils l’ont fait en interdisant aux développeurs d’exécuter leur propre code.

C’était le Cleanroom software engineering, développé par Harlan Mills chez IBM dans les années 1970. Le nom vient de la fabrication de semi-conducteurs, où l’objectif est de prévenir les conditions qui créent des défauts. Vous ne testez pas la qualité à posteriori. Vous la concevez dès le départ.

Le problème : le testing trouve des défauts, il ne les prévient pas

La plupart de l’ingénierie logicielle part du principe d’une boucle écrire-tester-corriger. Vous écrivez du code, vous l’exécutez, vous trouvez des bugs, vous les corrigez. Cela donne l’impression d’être productif. C’est aussi mathématiquement garanti de laisser des défauts derrière soi.

Le testing ne peut prouver que la présence de bugs, jamais leur absence. Dijkstra l’a dit en 1969. Si votre code a mille chemins d’exécution possibles et que votre suite de tests en couvre cinquante, il vous reste 950 chemins non testés. Le taux de défauts moyen de l’industrie de 10-50 par KLOC n’est pas un échec de rigueur du testing. C’est le résultat prévisible d’un processus qui permet aux défauts d’exister.

Cleanroom inverse cela. Au lieu d’écrire du code puis de le tester, vous écrivez du code qui est correct par construction.

Comment Cleanroom fonctionne réellement

La méthode comporte trois pratiques rigides qui fonctionnent ensemble.

Premièrement, le développement incrémental sous statistical process control. Le projet est découpé en petits incréments, chacun ajoutant un sous-ensemble bien défini de fonctionnalités. Chaque incrément passe par la spécification, la conception, la vérification et le testing en tant qu’unité complète. La clé est que les taux de défauts sont mesurés par incrément. Si un incrément dépasse son objectif de défauts, le processus est interrompu et l’équipe détermine ce qui a mal tourné avec la méthode, pas avec les individus.

Deuxièmement, la conception fonctionnelle-théorique utilisant des box structures. Chaque composant logiciel est défini à trois niveaux :

  • Black box : Le comportement externe, défini purement comme une fonction mathématique des entrées vers les sorties. Pas de state, pas de détail d’implémentation.
  • State box : La fonction interne de transition de state. Quel state le composant maintient-il, et comment change-t-il ?
  • Clear box : L’implémentation réelle, construite à partir de composants vérifiés.

Chaque niveau est vérifié par rapport à celui du dessus avant de continuer. Vous n’écrivez pas la Clear box tant que la State box n’est pas prouvée correcte. Vous n’écrivez pas la State box tant que la Black box n’est pas prouvée correcte. Cela semble lent. Ce ne l’est pas, car vous ne debuggez pas. Vous ne passez pas d’après-midi dans un debugger. Le code fonctionne du premier coup à la compilation.

Troisièmement, et c’est la partie qui fait exploser les cerveaux : aucun execution testing par les développeurs. Les personnes qui écrivent le code ne le compilent pas, ne l’exécutent pas, ne font pas d’unit testing. Le testing est effectué par une équipe séparée utilisant des méthodes statistiques. Ils traitent le logiciel comme une Black box et génèrent des cas de test basés sur des usage profiles, pas sur la structure du code.

Cette séparation n’est pas une cruauté bureaucratique. C’est le mécanisme central qui force la correction par construction. Si vous savez que vous ne pouvez pas exécuter votre code pour “voir si ça marche”, vous êtes obligé de réfléchir à chaque cas avant de le taper. Vous ne pouvez pas compter sur le compilateur pour attraper vos erreurs, ni sur un rapide python script.py pour révéler le bug évident. Vous devez avoir raison.

Les chiffres qui comptent

La IBM Federal Systems Division a utilisé Cleanroom sur plusieurs projets dans les années 1980 et 1990. Les résultats étaient cohérents entre les équipes, les langages et les domaines d’application.

Le système de contrôle de satellite de la NASA : 0,1 défaut par KLOC lors du test final. Un système de facturation COBOL : 0,3 défaut par KLOC. Un système temps réel Ada : 0,4 défaut par KLOC. Comparez avec les moyennes industrielles de l’époque : 10-50 défauts par KLOC lors du unit test, 5-10 restant encore à la livraison.

Les équipes Cleanroom livraient aussi plus vite. Les données industrielles de l’époque montraient que 50-70 % de l’effort logiciel allait au testing et au debugging. Les équipes Cleanroom passaient ce temps sur la conception à la place, et le code fonctionnait du premier coup.

Pourquoi presque personne ne l’utilise

Si Cleanroom est si efficace, pourquoi tout le monde ne le fait-il pas ?

Vous ne pouvez pas “juste l’essayer” sur un sprint. La méthode est interdépendante. Le statistical process control ne fonctionne que si vous mesurez chaque incrément. Les box structures ne fonctionnent que si vous faites la vérification formelle. La règle de non-exécution ne fonctionne que si elle est absolue. L’adoption partielle ne vous donne aucun des bénéfices et tout l’overhead.

Le marché a aussi changé. Dans les années 1970 et 1980, le logiciel était livré sur bande. Un bug en production était coûteux à réparer. Aujourd’hui nous livrons over the wire, et nous pouvons patcher en quelques minutes. L’incitation économique pour un logiciel zero-defect s’est affaiblie.

La plupart d’entre nous aiment aussi exécuter notre code. La boucle de feedback immédiate d’écrire-exécuter-corriger est gratifiante. Cleanroom vous demande de retarder cette gratification jusqu’à ce que vous ayez complètement pensé au problème. Pour beaucoup de développeurs, cela ressemble à essayer d’écrire de la prose sans être autorisé à la relire.

Ce que vous pouvez reprendre sans adopter toute la religion

Vous ne pouvez probablement pas implémenter le Cleanroom complet dans votre entreprise. Votre manager vous regarderait bizarrement. Mais vous pouvez adopter des morceaux et en retirer de vrais bénéfices.

Écrivez l’interface avant l’implémentation. Définissez les entrées, les sorties et les preconditions avant d’écrire le body. Déclarez ce que la fonction promet, pas seulement ce qu’elle fait.

# 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

C’est une fonction triviale, mais la discipline passe à l’échelle. Pour un exemple plus complexe, définissez une state machine explicitement avant de l’implémenter.

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

En encodant explicitement les transitions valides, vous rendez les états illégaux unrepresentable. Le code ne peut pas entrer dans un état invalide parce que la state machine l’en empêche. C’est la philosophie des box structures en miniature.

Faites tester votre code par quelqu’un d’autre. La séparation d’autorité et de vérification est l’idée la plus radicale de Cleanroom et la plus transférable. Quand vous testez votre propre code, vous testez vos propres hypothèses. Quelqu’un d’autre essaiera le cas que vous n’avez pas considéré parce qu’il semblait “évident” qu’il ne se produirait pas.

Mesurez la defect density par unité de travail. Suivez combien de bugs échappent à chaque phase de votre processus. Si votre sprint livre systématiquement avec des integration bugs, le problème n’est pas des développeurs négligents. Le problème est que votre processus permet aux integration bugs d’exister. Corrigez le processus.

Où cela s’effondre

Cleanroom n’est pas une solution universelle. Il fonctionne mieux quand les requirements sont stables et que la correction compte plus que le time-to-market. Il fonctionne mal pour le développement exploratoire et le rapid prototyping, où l’objectif est de découvrir ce que les utilisateurs veulent plutôt que d’implémenter correctement une spécification connue.

Il nécessite aussi un management buy-in. Vous ne pouvez pas faire du Cleanroom en secret. Le statistical process control nécessite la collecte de données dans toute l’équipe. La règle de non-exécution nécessite que la direction l’applique réellement, même quand les deadlines approchent et que les développeurs veulent “juste vérifier rapidement si ça marche”.

La conclusion

Les 0,1 défaut par KLOC d’IBM n’étaient pas un miracle. C’était le résultat d’un processus conçu pour prévenir les défauts plutôt que de les détecter. Les pratiques spécifiques – box structures, statistical testing et isolement des développeurs – peuvent sembler étrangères à la culture logicielle moderne. Mais le principe sous-jacent est intemporel : il est moins cher de réfléchir avant de taper que de debugger après.

Vous n’avez pas besoin d’adopter Cleanroom en bloc. Commencez par une fonction. Écrivez son contract avant son body. Faites réviser la logique par un collègue avant de l’exécuter. Suivez d’où viennent vos bugs et corrigez le processus qui les produit. Zero defects par KLOC n’est probablement pas votre objectif. Mais passer de 50 à 5 est réalisable, et la méthode est la même. Réfléchissez d’abord. Écrivez ensuite. Exécutez en dernier.