1980年代のソフトウェア業界平均は、1000行あたり30〜60の欠陥だった。IBMのCleanroomチームは、2万行のコンパイラインクリメントをテストで53の欠陥を発見した状態で出荷した。これはKLOCあたり2.6である。1万行の個別インクリメントの中には、システムテストで欠陥が全く見つからなかったものもあった。
最も奇妙な点?プログラマーは自分のコードを実行することを禁じられていた。
Cleanroom software engineeringが実際に意味すること
1980年代にIBMの数学者ハーラン・ミルズによって開発されたCleanroom software engineeringは、Unit Testingとデバッグの代わりにformal specification、structured design、mathematical correctness verificationに依存する理論に基づいたプロセスである。その名前は半導体製造から来ている。チップ工場では、埃を入れて後から拭き取ることはしない。最初から汚染を防ぐのだ。
Cleanroomでは、開発者はUnit Testingを行わない。デバッグもしない。検証する。
このプロセスはソフトウェアをインクリメントに分割する。典型的には5000〜15000行のコードである。各インクリメントは仕様化され、設計され、検証され、そして完全な単位として統計的にテストされる。開発者はコードを書くが、開発中にコンパイルや実行をしてはいけない。コードが初めて実行されるのは、正式なシステムテストの際である。
box structure verificationがデバッグを置き換える仕組み
中核となるメカニズムはbox structure specificationである。各コンポーネントは3つのレベルで定義される。
black boxは外部動作を仕様化する。内部状態に言及せず、刺激と応答を定義する。
state boxは内部状態変数と状態遷移関数を追加する。
clear boxは実際の実装であり、state boxの構造化された改良でなければならない。
この階層が重要なのは、各レベルで独立して正しさを検証できるからである。state boxがblack boxを実装することを証明し、clear boxがstate boxを実装することを証明する。
自明でないcorrectness argumentを持つ関数の実践例を以下に示す:
from typing import Optional
def binary_search(arr: list[int], target: int) -> Optional[int]:
"""
Black box spec:
Pre: arr is sorted in non-decreasing order.
Post: Returns index i such that arr[i] == target,
or None if target is not present.
"""
low, high = 0, len(arr) - 1
while low <= high:
mid = (low + high) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
low = mid + 1
else:
high = mid - 1
return None
ループに対するverification argumentが重要である。チームはこれを共同でレビューし、3つの事実を確認する。第一に、arr[mid] == targetの場合、postconditionは即座に満たされる。第二に、arr[mid] < targetの場合、targetはmidより大きなインデックスにしか存在しえないため、low = mid + 1と設定することで、targetが存在するならばそれがarr[low:high+1]にあるというinvariantが保持される。第三に、arr[mid] > targetの場合、high = mid - 1について対称的な議論が成り立つ。
これは、空の配列を考慮したかどうか誰かが尋ねるコードレビューではない。あらゆる可能な入力が指定された出力を生み出すという、構造化されたグループ証明である。
零欠陥の主張の背後にあるIBMの数字
IBMは1980年代後半から1990年代初頭にかけて、3つの大規模プロジェクトにCleanroomを適用した:COBOL Structuring Facility(4万行)、空軍のヘリコプター飛行プログラム(3万5000行)、NASAの宇宙輸送計画システム(4万5000行)である。
COBOL/SFのデータが最も詳細である。最初の2万行インクリメントは、formal specifications、box structure design、グループcorrectness verificationを用いて開発された。開発者は開発中に自分のモジュールをコンパイルしたり実行したりすることが許可されなかった。コードはそのままシステムテストに入った。
結果:テスト中に53の欠陥が発見された。すべての欠陥の90%以上は、コードが実行される前の検証フェーズで捕捉された。
比較のため、当時の従来型IBMプロジェクトでは、実行前に約60%の欠陥が見つかった。Cleanroomはその比率を逆転させた。
一部のインクリメント、特に1万行未満の小さなものでは、システムテストで欠陥がゼロだったと報告された。ここから「1万行で零欠陥」という主張が生まれた。それは実際に起こった。普遍的ではなかったが、IBMが標準ベンチマークにするほど再現性があった。
なぜ自分のコードを実行しないとバグが減るのか
これは開発者の頭を混乱させる部分である。テストしないことがどうやってより良いコードを生み出すのか?
答えは認知的であり、技術的ではない。自分の仕事を確認するためにコードを実行できないことを知っていると、より注意深く設計する。より小さな関数を書く。入力する前にエッジケースを考える。安全網がないため、型システムと構造化プログラミングに依存する。
これは外科医がチェックリストを使うのと同じ理由である。制約が異なる精神モードを強制するのだ。
統計的品質管理のレイヤーも存在する。Cleanroomはoperational profileに基づいたstatistical usage testingを使用する。テストケースは、開発者のバグの所在に関する推測ではなく、実際のユーザー行動の確率分布から抽出される。つまり、バグを狩るだけでなく、信頼性を測定しているのだ。
Cleanroomをニッチに留めたトレードオフ
Cleanroomは世界を征服しなかった。理由がある。
第一に、訓練の障壁は厳しい。formal specificationsを書き、mathematical correctness argumentsを構築できるチームが必要だ。2025年時点で、大多数のCS卒業生は非自明な関数の形式的証明を行ったことがない。
第二に、事前設計コストが高い。IBMはCOBOL/SFプロジェクトで、仕様書の文章量が設計書の4倍を超えたと報告した。設計時間とテスト時間を交換しているのだ。これはコンパイラや飛行ソフトウェアには有効だ。週ごとに要件が変わるCRUDアプリには有効ではない。
第三に、零欠陥の主張は欠陥密度の話であり、すべてのバグが存在しないという話ではない。Cleanroomのインクリメントでも仕様書の誤りはありうる。black boxが間違っていれば、検証済みのclear boxも構造的に間違っている。
官僚主義なしにCleanroomの規律を盗む方法
おそらく、完全なCleanroomを採用することはできない。プロダクトマネージャーが4対1の仕様対コード比率を待つことはないだろう。しかし、高価値な部分は盗むことができる。
1. 実装より先にcontractを書く。
preconditions、postconditions、invariantsを使ってblack boxを定義する。非公式なコメントであっても、最適化の前に境界について考えることを強制する。
from typing import List, Tuple
def partition(nums: List[int], pivot: int) -> Tuple[List[int], List[int]]:
"""
Black box spec:
Pre: True (any list of integers is valid).
Post: left contains exactly the elements of nums where x <= pivot.
right contains exactly the elements of nums where x > pivot.
len(left) + len(right) == len(nums).
"""
left = [x for x in nums if x <= pivot]
right = [x for x in nums if x > pivot]
# Runtime checks act as lightweight verification witnesses.
assert all(x <= pivot for x in left)
assert all(x > pivot for x in right)
assert len(left) + len(right) == len(nums)
return left, right
2. 一部のUnit Testsをverification argumentsに置き換える。
テストを書く前に、なぜコードが正しいかという一文の議論を書く。その一文を構築できなければ、設計が複雑すぎる。これが現代のチームにとって最も効果的なCleanroomの実践である。
3. property-based testingをstatistical usage testingとして使う。
PythonのHypothesisやJavaScriptのfast-checkなどのツールは、分布から入力を生成する。これは例に基づくUnit Testsよりも、Cleanroomのstatistical testingに精神的に近い。
4. コンパイルを検証から分離する。
一行書いてコンパイルし、タイポを直して次の行を書くという習慣があるなら、それは反射的なデバッグである。実行する前に完全な論理単位を書いてみる。その不快感こそがポイントなのだ。
FAQ
Cleanroomは今も使われているか?
安全性が重要な領域やミッションクリティカルな領域で生き残っている。NASA、FAA、一部の医療機器メーカーがこのプロセスのバリエーションを使用している。商用ソフトウェアでは珍しい。
本当にUnit Testingなしでコードを出荷できるか?
それと同等に厳密なものに置き換える場合に限る。Cleanroomチームは、ほとんどのチームがテストに費やす時間よりも多くの時間を検証に費やした。時間が消えたわけではない。左に移動しただけである。
Cleanroomはゼロバグを保証するか?
いいえ。実装が高い確率で仕様書と一致することを保証するだけである。仕様書が間違っていれば、バグは完璧に保存される。
生産性への影響はどうだったか?
IBMはCOBOL/SFプロジェクトで、1人月あたり400行以上のコード生産性を報告した。これは主に、大幅に削減されたテスト時間が増大した設計労力を相殺したためである。
結論
次に、ある方法論がzero-defectソフトウェアを提供すると主張する者が現れたら、プロジェクトデータを求めよう。IBMのCleanroomの数字は実在するが、それは特定の文脈から生まれた:経験豊富なチーム、形式的訓練、インクリメンタルな提供、そしてデバッグではなく検証する意志。
1万行の零欠陥インクリメントは達成可能である。ただ、それはほとんどの組織が支払うことを惜しむ以上の先見の明を必要とする。