原題: Out of the Tar Pit 著者: Ben Moseley、Peter Marks 公開日: 2006-02-06 ソースURL: MoseleyMarks06a.pdf アーカイブ日: 2026-06-11
この論文は、ソフトウェア工学の古典であるBrooksの「No Silver Bullet」を正面から継承しつつ、その中心的主張に真っ向から異を唱える。Brooksはソフトウェアの複雑性の大部分が「本質的(essential)」だと論じたが、MoseleyとMarksは「いや、そのほとんどは偶発的(accidental)だ」と反論する。そして、その偶発的複雑性の最大の発生源が「可変状態(mutable state)」と「制御フロー(control flow)」にあると喝破し、複雑性を根本から回避・分離するアーキテクチャを提案する。
著者らはまず、ソフトウェア開発のあらゆる問題——信頼性の欠如、納期遅延、セキュリティ脆弱性、パフォーマンス劣化——の根本原因が複雑性にあると断じる。システムを理解できなければ、これらすべてを防ぐことはできない。Dijkstraの「我々は自ら作り出した複雑性に押しつぶされないよう、それを明晰で、もつれのない、単純なものに保たねばならない」という言葉や、Hoareの「信頼性の代償は、徹底的な単純さの追求である」というTuring賞講演を引用し、複雑性こそがソフトウェア危機の核心だと位置づける。
システムを理解するための二つの主要アプローチ——テストと非形式的推論(informal reasoning)——を検討したうえで、著者らはテストに対して極めて厳しい態度をとる。「ある入力セットでのテストは、別の入力セットでの振る舞いについて何も教えてくれない」というDijkstraの洞察を引き、「テストはバグの存在を示すことはできても、バグの不在を示すことは決してできない」と断言する。したがって、バグを作り込まないことの方が、バグを検出することよりも重要であり、そのためにはシステムの単純さが不可欠だと説く。
著者らは複雑性の主要因を三つに分類する。
第一に状態。 これは最も深刻な原因である。サポートデスクに電話して「再起動してみてください」「再インストールしてみてください」と言われた経験は誰にでもある——これらはすべて状態に起因するバグの症状だ。可変状態がもたらす問題は、テストと推論の両面に及ぶ。状態を持つシステムでは、ある内部状態で成功したテストが、別の内部状態では何の保証にもならない。加えて、状態は「汚染(contamination)」を引き起こす——たとえ自分が書いたコードが純粋に状態を持たなくても、間接的にでも状態のあるコードを呼び出せば、そのコード全体が状態の文脈でしか理解できなくなる。著者らは「ラクダの鼻をテントに入れたら、ラクダの全身が後を追う」と警鐘を鳴らす。さらに、状態ビットが一つ増えるごとに可能な状態数は倍増する——これが指数関数的爆発を生む。
第二に制御。 ほとんどのプログラミング言語は、テキスト上の文の順序によって暗黙的に実行順序を規定する。しかし多くの場合、プログラマは順序を気にしていない——三つの独立した代入文を書いたとき、プログラマは値の関係だけを指定したいのであって、どの順で計算されるかはどうでもいい。ところが命令型言語の意味論は、この不要な順序指定を強制する。プログラマは「what」ではなく「how」を書かされているのだ。さらに、この人工的な順序付けは、コードを読む人間に「この順序は意味があるのか、そうでないのか」という余計な推論を強いる。並行性(concurrency)はこの問題をさらに悪化させる——共有状態並行性では、同じ入力・同じ初期状態でテストを再実行しても、同じ結果が得られる保証すらない。
第三にコード量。 状態と制御を管理するために書かれるコードが、システム全体のコード量を膨張させる。Brooksが指摘したように、複雑性はコードサイズに対して非線形に増大する。したがってコード量の最小化は死活問題だが、著者らは状態と制御を適切に管理すれば、コード量と複雑性の非線形な関係は緩和できると楽観視する。
このほかにも「複雑性は複雑性を生む(Complexity breeds complexity)」「単純さは難しい(Simplicity is Hard)」「力は腐敗する(Power corrupts)」という三つの二次的原則が提示される。とくに「力は腐敗する」は重要だ——言語の表現力が高いほど、その言語で書かれたシステムを理解するのは難しくなる。これがGC(ガベージコレクション)が良い理由であり、状態を許可する言語への警戒の理由でもある。
著者らは三つの主要パラダイムを複雑性管理の観点から評価する。
オブジェクト指向は状態をカプセル化によって管理しようとするが、カプセル化は単一オブジェクト内の制約には有効でも、複数オブジェクトにまたがる制約には弱い。さらにOOPの本質である「オブジェクト同一性(intensional identity)」は、値として扱いたい場面でも不要な同一性概念を持ち込み、推論を複雑にする。結局のところ、OOPは状態に依存しており、状態に起因する問題から逃れられない。
関数型プログラミングは参照透過性によって状態問題を根本から解決する。同じ引数には常に同じ結果——これによりテスティングの最大の弱点の一つが消滅する。しかし、現実のシステムの多くは何らかの状態を必要とし、純粋関数型だけでは対応しきれない。Haskellのモナドはこの問題に対する洗練された解決策だが、「状態を持つサブ言語をHaskell内部に作れてしまう」ために、回避しようとした問題を再導入するリスクがある。また、関数型で状態をシミュレートするためにすべての関数に追加のパラメータを通す方法は、参照透過性は保たれるものの、パラメータが巨大化して実質的に推論の助けにならなくなる危険をはらむ。著者らは、これは関数型アプローチの一般的な力を損なうものではないとしつつ、状態が不可避な領域での関数型の弱さを率直に認める。
論理型プログラミングは制御からの解放を約束する点で最もラディカルだ。理想的な論理型プログラミングでは、プログラムは公理の集合に過ぎず、「実行」とは証明の構築に他ならない——制御の概念は完全に分離される。しかしPrologをはじめとする実際の論理型言語は、実行順序(左から右、上から下)やカット(cut)などの手続き的要素を持ち込み、この理想から後退している。
Brooksの定義をより厳格に再解釈し、著者らは「本質的複雑性」を「ユーザーの問題に内在する複雑性」と定義し直す。ユーザーが知りもしない概念——スレッドプール、ループカウンタ、キャッシュ——は、定義上、本質的ではありえない。この定義に従えば、現代の大規模システムに見られる複雑性の大半は偶発的だと結論づけられる。
著者らの推奨は明確だ。回避(Avoid)と分離(Separate)である。偶発的な複雑性は可能な限り回避し、回避できないものは純粋なロジックから分離せよ。
理想世界の思考実験が説得力を増す。理想世界ではパフォーマンスを気にする必要がなく、言語とインフラは完全な支援を提供する。この世界で必要とされるのは、ユーザーの非形式的な要求から形式的な要求を導出し、それをそのまま実行するだけだ。これは宣言的プログラミングの本質そのものであり、「what」だけを指定すればよい世界である。
この理想世界でデータを分類すると、入力データのうち将来参照されるものだけが「本質的状態」であり、派生データはすべて——可変であれ不変であれ——再導出可能なので「偶発的状態」だと判明する。キャッシュや導出値の格納はすべて偶発的なのだ。制御に至っては、理想世界では完全に不要である。これは論理型プログラミングが教える教訓だ。
現実世界では、パフォーマンスのために偶発的状態(キャッシュなど)や偶発的制御(並列評価の指示など)を再導入せざるをえない。しかし決定的に重要なのは、それらを「宣言的に指定」し、インフラに管理を任せることで、アプリケーションロジックが状態不整合のリスクに晒されないようにすることだ。Kowalskiの有名な定式「アルゴリズム = ロジック + 制御」を引きながら、著者らは制御(および偶発的状態)をロジックから完全に分離するアーキテクチャを提唱する。
提案されるアーキテクチャは三つの仕様からなる。
本質的状態(Essential State):システムの基礎。何が入力として保存されるべきかを定義する。他のどのコンポーネントも参照しない。変更は他の仕様に影響しうるが、この仕様が他の仕様の変更によって影響されることはない。
本質的ロジック(Essential Logic):「ビジネスロジック」にあたる部分。状態に関して何が真でなければならないかを表現する。状態を変更することはなく、偶発的コンポーネントへの参照も持たない。
偶発的状態と制御(Accidental State and Control):パフォーマンスのためのヒント。他のどのコンポーネントからも参照されず、変更が他の仕様に波及することはない。概念的には最も重要度の低い部分である。
この三層分離の本質的価値は、各コンポーネントの「力(power)」を独立に制限できることにある。それぞれの層に特化した制限的な言語を用いることで、各層の推論が容易になる。これはドメイン特化言語(DSL)の考え方に近いが、対象とする「ドメイン」は抽象的な性質(状態・ロジック・パフォーマンス最適化)である。
論文の後半は、この原則に基づく具体的なアプローチとしてFRP(Functional Relational Programming)を提案する。FRPは名前の通り、関数型プログラミングとCoddのリレーショナルモデルを組み合わせたものである。
FRPにおける本質的状態はすべてリレーション(関係)として表現される。リレーションは集合であり、重複も順序も持たない——これは一般的な「テーブル」の概念とは異なる。リレーショナルモデルの最大の利点の一つは、アクセスパスへの依存を排除することだ。ネットワークモデルや階層モデル、そしてOOPやXMLでさえ、データ構造化の時点で将来のアクセスパスに関する主観的な判断を強制される。リレーショナルモデルではその必要がない——この「アクセスパス独立性」こそが、リレーショナルモデルが先行モデルに勝利した主因である。
本質的ロジックはリレーショナル代数(制限、射影、結合、和、差、積、商、交差の8演算)を純粋関数で拡張したもので表現される。導出リレーション(ビュー)の定義は、単なる等式の集合であり、実行順序の概念を完全に排除している。また、整合性制約(integrity constraints)が宣言的に指定され、インフラが常にそれを強制する——トリガーやメソッドのような手続き的機構は一切使わない。この宣言的制約の利点は決定的だ。制約同士が相互参照できないため、制約を追加しても複雑性は線形にしか増加しない。OOPのようにメソッド間の相互作用で複雑性が爆発することはない。
偶発的状態と制御は「パフォーマンスヒント」の集合として宣言的に指定される。たとえば、ある導出リレーションを実際に保存(キャッシュ)するよう指示したり、特定のカラムを別ストレージに分離したり、評価戦略(先行評価か遅延評価か)を指定したりする。これらのヒントは互いに独立で、除去してもシステムの正しさは変わらない。遅くなるかもしれないが、壊れはしない。
外界とのインターフェースはフィーダー(入力変換)とオブザーバー(出力駆動)として設計される。フィーダーは外部からの入力をリレーション代入に変換し、オブザーバーは導出リレーションの変化を監視して外部出力を生成する。この接続層は必要最小限にとどめ、システムの中核ロジックには手を触れさせない。
論文では不動産管理システムを例に、FRPの各コンポーネントを具体的に示している。6つの基底リレーション、3つのユーザー定義関数、13の導出リレーション、10の整合性制約が宣言的に定義され、偶発的状態として「PropertyInfoをキャッシュせよ」「RoomとFloorを非正規化して共有ストレージに格納せよ」といった3つの宣言的ヒントが添えられる。この具体例によって、FRPが机上の空論ではなく、現実的なシステム記述に適用可能であることが示される。
論文の随所で、著者らは不必要なデータ抽象化に対しても批判の目を向ける。複合データ型を作り、その内部を隠蔽することは、参照透過性の利点を州侵食する。関数に渡されるデータのうち、実際に関数の結果に影響する部分だけが見えなくなり、テストも推論も難しくなる。OOPやXMLのような入れ子構造は、データ間に主観的なグルーピングを押し付ける点で、かつての階層モデルやネットワークモデルと同じ過ちを繰り返しているというわけだ。リレーショナルモデルが平坦なリレーションのみを扱い、積型(product type)の定義を許さないのは、この問題を回避するための意図的な設計選択である。
2006年に書かれたこの論文は、2026年の現在から読み返しても驚くほど先見的だ。FRPという具体的な提案自体は主流になることはなかったが、「状態こそが複雑性の元凶であり、可能な限り排除し、不可避なものは隔離せよ」という中心的主張は、その後のソフトウェア工学の方向性を正確に予言していた。ReactやReduxの登場、関数型プログラミングの再興、イミュータブルデータ構造の普及、CQRSやイベントソーシングといったアーキテクチャパターン——これらはすべて、状態とロジックの分離という「Out of the Tar Pit」の根本思想の異なる現れ方だと見ることができる。論文が予見していた「制約を宣言的に指定し、インフラに強制させる」という発想は、型システムの進化(Rustの所有権モデル、TypeScriptの構造的型付け)にも通底している。
とはいえ、FRPの実装が1500行のSchemeで済むという主張には、いくぶん楽観が過ぎる印象も否めない。現実の大規模システムでは、インフラストラクチャの複雑性がシステム全体の複雑性をはるかに上回る。分散トランザクション、ネットワーク分断、結果整合性といった分散システムの困難は、リレーショナル代数の枠内だけではとうてい扱いきれない。また、パフォーマンスヒントの「宣言的」指定という発想も、実際にはどの程度まで宣言的であり続けられるのか——データベースの世界でクエリオプティマイザの限界が常に問題になることを思えば、FRPインフラの最適化にも同様の壁が立ちはだかるだろう。
それにもかかわらず、この論文の永続的価値は、設計者が「これは本質的な複雑性だ」と受け入れてしまう前に立ち止まり、「本当にそうか?」と問い直すための概念フレームワークを提供した点にある。Brooksでさえ見落とした偶発的複雑性の巨大さを暴き出し、状態・制御・コード量のトリアーデを我々の目の前に突きつけた功績は、ソフトウェア工学の歴史のなかで色褪せることがない。