IBMのCleanroom software engineering processは、千行あたり0.1のdefectを達成した。当時の業界平均は10〜50だった。このprocessは文書化され、複数のプロジェクトと言語を横断してreplicateされ、独立して検証された。そして消えた。
より優れたmethodがそれを置き換えたわけではない。Zero-defect engineeringが消えたのは、それを価値あるものにしていた経済的条件が変化し、それを可能にしていた条件が handful of government contractors の外に広がらなかったからだ。
問題:defectは最初から設計されていた
ほとんどのsoftwareは、bugが避けられないという黙示的な前提の上に構築されている。codeを書き、実行し、問題を見つけ、修正する。このloopは生産的に感じられる。それはまた、あなたのprocessがdefectを通常のoutputとして生み出しているという譲歩でもある。
Cleanroomはこの前提を完全に拒否した。1970年代にIBMのHarlan Millsによって開発されたこのmethodは、softwareを半導体製造のように扱った。chipを製造した後に品質をテストし込むのではない。defectを生み出す条件を最初から防ぐのだ。
このmethodには3つの相互に連動するpracticeがあった。Statistical process controlの下でのincremental development。Black-box、state-box、clear-box構造を使ったfunction-theoretic design。そして最も頭を悩ませたrule:codeを書いた人々は、そのcodeを実行することを禁じられていた。
これはsadismではない。Forcing functionだった。codeを実行して動作確認ができなければ、タイプする前にcorrectnessについて論理的に考えなければならない。
数値は偶然ではなかった
IBM Federal Systems DivisionはNASAのsatellite control systemにCleanroomを適用し、最終テストでKLOCあたり0.1のdefectを測定した。COBOLのbilling systemは0.3を達成した。Adaのreal-time systemは0.4を達成した。結果はチーム、アプリケーションドメイン、プログラミング言語を横断して維持された。
このprocessはscheduleも圧縮した。当時の業界データは、software effortの50〜70パーセントがtestingとdebuggingに費やされていたことを示していた。Cleanroomチームはその時間を代わりにdesignに費やした。codeは初めてコンパイルされたときに動作した。
なぜ機能していたprocessが放棄された
もしこのmethodがこれほど効果的だったなら、なぜ消えたのか?
短い答えは、1990年から2010年の間にsoftware economicsが反転したからだ。長い答えは、zero-defect engineeringはもはや存在しない世界向けに最適化されていたからだ。
1970年代と1980年代、softwareは物理メディアで出荷された。productionでのbugはrecall、patch disk、またはオンサイトの技術者を必要とした。defectのコストは莫大だった。Zero-defect engineeringは高価だったが、1回のrecallを回避すれば全体のprocessを賄えた。
Webはcost functionを変えた。今日私たちはover the wireでdeployする。bugがproductionに到達しても、数分でrollbackし、数時間でpatchする。1つのdefectのコストは桁違いに下がった。Zero-defect engineeringのコストはまったく下がらなかった。依然としてformal verification、statistical process control、そして開発者が自分のcodeを実行しないということが必要だ。これらのpracticeは、現代のproduct developmentが費やすことを拒否する時間を消費する。
相互依存の罠
Cleanroomはメニューではない。気に入った部分だけを採用することはできない。
Statistical process controlは、すべてのincrementを測定し、defect targetを見逃したときに停止する場合にのみ機能する。Box structuresは、次を書く前に各レベルを検証する場合にのみ機能する。No-execution ruleは絶対的であれば機能する。「これがコンパイルするかちょっと確認するだけ」という開発者はforcing functionを破壊する。
部分的な採用は、どのbenefitももたらさず、すべてのoverheadを負わせる。チームは1つのsprintでCleanroomを試すことはできない。彼らはサイクル全体を再構築し、すべての開発者を再教育し、数か月にわたってprocess dataを収集しなければならない。このproposalに直面したほとんどのmanagerは、代わりに別のQA engineerを雇う。
Velocity premiumがquality premiumを殺した
現代のsoftwareはtime-to-marketで競争する。consumer softwareにおけるfirst mover advantageは、発売後にbugを修正するコストよりも価値がある。投資家はdefect density metricsではなくgrowth curvesを報いる。
Zero-defect engineeringは異なるscoreboard向けに最適化されている。correctnessが主要なconstraintであり、schedule pressureが二次的であると仮定している。これはNASAのsatellite control systemでは真実だった。競合より先にshipしようとするstartupには当てはまらない。
文化的なミスマッチはもっと深い。開発者はcodeを実行するのが好きだ。write-run-fixのimmediate feedback loopは満足感がある。Cleanroomは、すべてのedge caseを考え抜くまでその満足感を先延ばしにすることを求める。
業界は異なるsocial contractを選んだ。私たちはspeedと引き換えにbugを容認し、continuous feedbackと引き換えにそれらをpatchする。これは合理的なtradeだ。それもまた、平均的なweb applicationのdefect rateがIBMのCleanroom数値よりも1980年代平均に近い理由だ。
何と引き換えにしたか
置き換えは、correctnessよりiterationを優先する一連のpracticeだった。
Continuous integrationはbug発見を速くしたが、codeをcorrect by constructionにはしなかった。Agileはfeedback loopsを短縮したが、design phasesも短縮した。Test-driven developmentは依然として、Cleanroomが拒否したwrite-test-fix loopを前提としている。
これらのどれもbad practiceではない。現代のstackは、ユーザーが何を望んでいるかを発見することを最適化している。Zero-defect engineeringは、既知のspecificationを正しく実装することを最適化していた。
システム全体を採用せずに盗めるもの
あなたの会社で完全なCleanroomを実装することはできないだろう。しかし、underlying principlesは転用でき、いくつかの現代のtoolはそのoverheadなしにmethodのrigourを近似する。
Contractsを書け、commentsだけでなく。 Preconditionsとpostconditionsを明示的に定義し、code内でenforceする。
from dataclasses import dataclass
from decimal import Decimal
@dataclass(frozen=True)
class Transfer:
from_balance: Decimal
to_balance: Decimal
amount: Decimal
def execute(self) -> tuple[Decimal, Decimal]:
# Preconditions stated and checked
assert self.amount > 0, "transfer amount must be positive"
assert self.from_balance >= self.amount, "insufficient funds"
new_from = self.from_balance - self.amount
new_to = self.to_balance + self.amount
# Postcondition: total value is conserved
assert new_from + new_to == self.from_balance + self.to_balance
return new_from, new_to
仮定をexecutable checksとしてencodeすることで、暗黙のreasoningを明示的なguardsに変換し、development中にedge casesを考え抜くことを強制する。
Property-based testingをstatistical process controlとして使え。 Cleanroomはusage profilesに基づくstatistical testingを使用した。現代のproperty-based testing toolは、random inputsを生成しinvariantsを検証することで、類似のことを行う。
from hypothesis import given, strategies as st
from datetime import datetime, timedelta
@given(
start=st.datetimes(min_value=datetime(2000, 1, 1)),
delta=st.timedeltas(min_value=timedelta(0), max_value=timedelta(days=365))
)
def test_duration_roundtrips(start, delta):
"""Adding then subtracting the same duration must return the original."""
assert start + delta - delta == start
このtestは巨大なinput spaceにわたってmathematical propertyをassertする。反例を見つけると、minimal failing caseにshrinkする。あなたはspecific examplesではなく、computationの構造を確認している。
Invalid statesをunrepresentableにしろ。 Cleanroomのbox structure methodologyは、詳細度を増すレベルで振る舞いを定義することに関わっていた。現代の同等物は、illegal statesを防ぐためにstatic typesを使うことだ。
from typing import NewType
UserId = NewType("UserId", int)
OrderId = NewType("OrderId", int)
def fetch_order(order_id: OrderId) -> dict:
...
# This will not compile in a typed codebase:
# fetch_order(UserId(42)) # type error: expected OrderId, got UserId
Type checkerは、いかなるtestよりも前に実行されるverification layerになる。UserIdをOrderIdが必要な場所に渡すことはできない。なぜならtype systemがそのミスを構造的に不可能にするからだ。
これがいまだに重要な場所
Zero-defect engineeringは間違っていたわけではない。Nicheになったのだ。
人を殺すbugが1つでもあるsafety-critical domainでは依然として使用されている。Medical devices、avionics、nuclear control systemsは依然としてCleanroomから派生したprocessを使用している。なぜならdefectのコストは依然として天文数字だからだ。FDAやDO-178Cのstandardsは、異なる名前の下で同じアイデアの多くを保存している。
私たちの残りにとって、web applicationのbugはsupport ticketと1回のdeployのコストだ。Pacemakerのbugは命のコストだ。Methodは機能しなくなったわけではない。私たちが必要としなくなったのだ。
不快な真実
Zero-defect engineeringが消えたのは、それが失敗したからではなく、業界がsuccessを再定義したからだ。目標は「初回から動作するsoftwareをshipする」から「壊れたversionがユーザーに気づかれる前に置き換えられるほど速くsoftwareをshipする」に移行した。
これは擁護可能なtradeだ。それは現代のinternetを築いた。これまた、ほとんどのsoftwareがdefectを残すことを数学的に保証したprocessで構築され、私たちがそれを受け入れている理由でもある。なぜなら、後で修正するのが今や十分に安価だからだ。
Cleanroomを採用しなくても、より良いcodeを書くことはできる。1つのfunctionから始めろ。そのbodyより先にcontractを書け。Invariantsに対してproperty-based testsを実行しろ。Typesを使ってillegal statesを到達不可能にしろ。Bugsがどこから来るかをtrackし、それらを生み出すprocessを修正しろ。
KLOCあたりzero defectsはおそらくあなたの目標ではない。しかし、このmethodがなぜ機能したか、そしてなぜ私たちが気にしなくなったかを理解することは、あなたのprocessが実際に何を最適化しているかについて重要なことを教えてくれる。ほとんどのチームはこの選択を明示的に行ったことはない。彼らは数十年前にvelocityをcorrectnessより選んだ業界からそれを受け継ぎ、振り返らなかった。