/note/tech

Out of the Tar Pit

要約:

■ 1. 論文の概要

  • 大規模ソフトウェアシステムの開発における最大の困難は「複雑性」である
  • Brooks の「本質的(essential)」と「偶発的(accidental)」の区別を踏まえつつ、現代のシステムに残る複雑性の大半は本質的ではないと主張する
  • 複雑性の主要な原因を特定し、関数型プログラミングとCoddのリレーショナルモデルを組み合わせたアプローチ(Functional Relational Programming: FRP)によって複雑性を最小化する方針を提示する

■ 2. 複雑性の重大性

  • 複雑性はソフトウェアの信頼性低下、納期遅延、セキュリティ欠陥、性能問題の根本原因である
  • システムを「理解」できることがこれらすべての問題を回避する前提条件であり、複雑性はその理解を破壊する
  • Dijkstra、Hoare、Backus、Corbatóなど多くの先人が複雑性の危険と簡潔性の重要性を指摘している
  • 「簡潔性は難しい(Simplicity is Hard)」が現実であるが、本論文はその達成に向けた楽観的な見通しを示す

■ 3. システムを理解するためのアプローチ

  • テスト(Testing):
    • システムを外部から「ブラックボックス」として観察する
    • 特定の入力セットに対する挙動は、異なる入力に対する挙動について何も保証しない
    • テストはバグの存在を示せるが、不在を証明できない(Dijkstra)
  • 非形式的推論(Informal Reasoning):
    • システムを内部から検討し、より正確な理解を得る
    • 両手法のうち非形式的推論がはるかに重要であり、改善により「エラーが作られる数を減らす」効果がある
    • テストの改善は「エラーが検出される数を増やす」にとどまる
  • 簡潔性の優位性:
    • テストと推論の両方に限界があるため、簡潔性がいずれの手法よりも重要である
    • テストへの投資より簡潔性への投資の方が、将来のあらゆる理解努力を支援する

■ 4. 複雑性の原因

  • 状態(State):
    • 最大の原因であり、プログラムの理解を困難にする
    • テストへの影響: ある状態でのテスト結果は、別の状態での挙動について何も保証しない
    • 非形式的推論への影響: 状態の数が増えるごとに考慮すべきシナリオが指数関数的に増大する
    • 汚染(Contamination): ステートレスな手続きも、ステートフルな手続きを間接的に呼び出すだけで汚染され、状態を持つものとして扱わざるを得なくなる
  • 制御フロー(Control):
    • 処理の順序に関するもので、多くの場合プログラマはその順序を気にする必要がない
    • 命令型言語はテキスト順に暗黙の実行順序を規定し、プログラマに不要な順序指定を強制する(過剰仕様)
    • 並行性(concurrency): 共有状態の並行アクセスは非形式的推論とテストの両方をさらに困難にする
  • コード量(Code Volume):
    • 状態管理や制御指定の副産物として生じる二次的な原因
    • 複雑性はコード量に対して非線形に増大するため、コードを最小限に抑えることが不可欠
  • その他の原因:
    • 「複雑性が複雑性を生む」: システムを理解できないことで重複コードや不適切な再利用が生じる
    • 「簡潔性は難しい」: 最初の解決策は最も簡潔とは限らず、意識的に追求しなければ得られない
    • 「力は腐敗させる(Power corrupts)」: 言語が許容する機能が多いほど、そのシステムを理解しにくくなる

■ 5. 複雑性管理の古典的アプローチ

  • オブジェクト指向プログラミング(OOP):
    • 状態: オブジェクトは状態(ミュータブルな属性)と、それにアクセスする手続きの組み合わせ(カプセル化)
    • カプセル化の問題: 複数メソッドが同一状態にアクセスする場合、制約の施行が分散する; 複数オブジェクトにまたがる制約の表現が困難
    • オブジェクト同一性: 「強度的(intensional)同一性」(属性が同じでも別オブジェクト)がデフォルトであり、値オブジェクトとの使い分けが推論を複雑にする
    • 結論: OOPはステートと制御の両方に由来する複雑性を引き起こし、複雑性回避の基盤として不十分
  • 関数型プログラミング(FP):
    • 純粋FPは状態とサイドエフェクトを排除し、参照透過性(referential transparency)を実現する
    • 参照透過性によりテストが大幅に改善され、非形式的推論も容易になる
    • 制御については暗黙の左から右への順序があり、明示的な並行性は持たないが、状態がないため並列評価が安全
    • 主な弱点: 状態を必要とするシステム(多数の実際のシステム)への対応が困難
    • Haskell のモナドは回避策だが、容易にステートフルなサブ言語として乱用され得る
    • 状態とモジュール性のトレードオフ: 関数型では状態的変更を加える際にすべての呼び出し元を変更する必要があり、参照透過性と引き換えに保守の手間が増す場合がある
  • 論理プログラミング(Logic Programming):
    • 「何を」するかを公理で宣言し、インフラが解を導出するという理想を持つ
    • 制御からの完全な分離という点で最も魅力的
    • Prolog は純粋な論理プログラミングとは乖離があり、暗黙の深さ優先探索順序や「カット」などの制御要素が複雑性を生む

■ 6. 本質的複雑性と偶発的複雑性

  • 本質的複雑性(Essential Complexity): ユーザーの問題に内在する複雑性(ユーザーの視点で不可避なもの)
  • 偶発的複雑性(Accidental Complexity): 開発チームが理想的な言語・インフラがあれば対処不要な複雑性(性能上の制約や言語の不備に起因するもの)
  • Brooks の「複雑性はソフトウェアの本質的特性」という主張に反論し、現代システムの複雑性の大半は偶発的だと主張する
  • 本質的複雑性の定義はユーザーが知っていることに限定される(スレッドプールやループカウンタはユーザーには本質的でない)

■ 7. 推奨される一般的アプローチ

  • 理想世界における状態:
    • 入力データ(ユーザーが直接提供したもの)= 本質的状態
    • 本質的派生データ(不変)= 偶発的状態(再導出可能なため保持不要)
    • 本質的派生データ(可変)= 偶発的状態(逆関数が存在する場合、入力への変更として扱える)
    • 偶発的派生データ = 偶発的状態
    • 現実のシステムでは大多数の状態が偶発的であり、理想世界では排除できる
  • 理想世界における制御:
    • 制御は完全に偶発的であり、非形式的要件には通常現れない
    • インフラが制御を担い、システムの結果は実際の制御機構から独立すべき
    • 論理プログラミングのアプローチが制御分離の理想を示している
  • 現実的な制限:
    • 性能: 偶発的状態・制御を要することがある
    • 表現のしやすさ: 偶発的状態を用いた方がロジックを自然に表現できる場合がある
    • これらは「必要な偶発的複雑性(Required Accidental Complexity)」として認識し、管理する
  • 推奨方針:
    • 「回避(Avoid)」: 本当に必要でない状態と制御を完全に排除する
    • 「分離(Separate)」: 必要な複雑性をシステムの本質的なロジックから切り離す
    • システムを「本質的ロジック」「本質的状態」「偶発的状態と制御」に明確に分割する
    • 各コンポーネントを異なる(制限された)言語で記述することで、個別の推論を容易にする
    • 「Algorithm = Logic + Control」(Kowalski, 1979)という考え方が根底にある

■ 8. リレーショナルモデル

  • Codd が提唱したリレーショナルモデルはデータベースに限らず、データの構造化・操作・整合性維持の一般的アプローチ
  • 構造(Structure):
    • すべてのデータをリレーション(重複なし・順序なしのレコードの集合)で表現する
    • Base Relation(直接格納)と Derived Relation(View: 他のリレーションから定義)が存在する
    • アクセスパス独立性: 事前に主観的なアクセス経路を定める必要がなく、OOP・XML・階層モデルの弱点を克服する
  • 操作(Manipulation):
    • リレーショナル代数: Restrict、Project、Product、Union、Intersection、Difference、Join、Divide の8操作
    • 閉包性(closure)により操作を任意にネストできる
  • 整合性(Integrity):
    • 宣言的な制約(候補キー、外部キー、任意の複雑な条件)で不変条件を規定する
    • インフラが制約違反となる状態変更を拒否・制限する
  • データ独立性(Data Independence):
    • 論理モデルと物理ストレージ表現を明確に分離する
    • 本論文が推奨する「偶発的/本質的」分割と直接対応する重要な特性
  • 拡張(Extensions): 一般的な計算能力、集計演算子(MAX/MIN/COUNT/SUM)、グループ化・集約、属性名変更
  • SQLはリレーショナルモデルを正確に反映していないため注意が必要

■ 9. 関数型リレーショナルプログラミング(FRP)

  • FRPの概念:
    • 本質的コンポーネント(ロジックと状態)を関数型プログラミングとリレーショナルモデルで実装する仮説的アーキテクチャ
    • 現時点では完全には実証されていないが、広く実証済みの原則(リレーショナルモデル、関数型・論理プログラミング)に基づく
    • 主目標は複雑性の排除
  • アーキテクチャの4コンポーネント:
    • 本質的状態(Essential State): ベースリレーションの宣言型定義(ユーザーが直接入力したデータのみ)
    • 本質的ロジック(Essential Logic): 導出リレーションの定義、整合性制約、純粋ユーザー定義関数
    • 偶発的状態と制御(Accidental State and Control): パフォーマンスヒントの宣言的指定(キャッシュ、物理ストレージ形式、並列制御ガイダンス)
    • その他(Other): 外部世界へのインターフェース(フィーダーとオブザーバー)
  • 状態への利点:
    • 無用な偶発的状態を明示的に回避し、「悪い状態」に陥る可能性を排除
    • ロジックのエラーが状態を壊さない(修正はロジックの訂正のみで済む)
    • リブート・リスタートが不要
    • ロジックの観点から本質的状態は「定数」として扱える
    • 整合性制約を宣言的に課すことで制約追加時の複雑性増大が線形にとどまる
  • 制御への利点:
    • 本質的ロジックのリレーショナル部分には制御フローが存在しない
    • 明示的な並列性を排除し、必要に応じて分離された偶発的制御として指定
    • インフラが暗黙的並列化を実施できる
  • コード量・データ抽象化への利点:
    • 本質的なものへの集中と不要な偶発的複雑性の回避により自然とコード量が減少
    • 主観的なデータグループ化(データ抽象化)を最小限に抑え、アクセスパス独立性と参照透過性を保持
  • フィーダーとオブザーバー:
    • フィーダー(Feeder): 外部入力をリレーショナル代入に変換し本質的状態を更新する
    • オブザーバー(Observer): 導出リレーションの変化に応じて出力を生成する
    • 両者はインフラが整合性制約を強制した上で動作する
  • インフラ要件:
    • 本質的状態向け: リレーション保存・取得、状態操作言語、基本型、オプションの永続ストレージ
    • 本質的ロジック向け: リレーショナル式評価、基本関数群、ユーザー定義関数言語、型推論、整合性制約表現・強制
    • 偶発的状態と制御向け: 導出リレーションのストレージ管理、物理ストレージ機構の柔軟な指定(データ独立性)
    • フィーダー・オブザーバー向け: リレーショナル代入コマンド処理、リレーション変化時の通知

■ 10. FRPシステムの例: 不動産仲介業務

  • システム概要:
    • 売り物件、入札、売却決定、仲介手数料を管理する不動産仲介業のシステム
    • FRPの宣言的な性質を示すための実例
  • 本質的状態(6つのベースリレーション):
    • Property(物件情報)、Offer(入札情報)、Decision(売主の決定)、Room(部屋情報)、Floor(階情報)、Commission(手数料テーブル)
  • 本質的ロジック:
    • ユーザー定義関数: priceBandForPrice、areaCodeForAddress、datesToSpeedBand
    • 内部導出リレーション(10個): RoomInfo、Acceptance、Rejection、PropertyInfo、CurrentOffer、RawSales、SoldProperty、UnsoldProperty、SalesInfo、SalesCommissions
    • 外部導出リレーション(3個): OpenOffers、PropertyForWebSite、CommissionDue
    • 整合性制約: 候補キー・外部キー制約、全物件1室以上、自物件への入札禁止、売却後の入札禁止、PREMIUM帯掲載50件以下、1物件への入札10件以下
  • 偶発的状態と制御:
    • declare store PropertyInfo(パフォーマンス用キャッシュ)
    • declare store shared Room Floor(非正規化ストレージのヒント)
    • declare store separate Property (photo)(低頻度属性の分離ストレージ)
  • フィーダー・オブザーバー: ユーザー入力をリレーションに変換し、外部導出リレーションを観察・表示するシンプルな構成で、カスタムコーディングをほぼ必要としない

■ 11. 結論

  • 複雑性こそが大規模ソフトウェアの最大の問題であり、意識的に「回避」と「分離」の原則を最優先設計目標に据えなければならない
  • 複雑性を制御できなければ必然的に拡大し、初期の妥協が長期的な複雑性の連鎖を生む
  • 性能のための早期設計(過早最適化)は特に危険であり、シンプルなシステムの性能改善は複雑なシステムからの複雑性除去よりはるかに容易
  • FRPはその最有力な実装アプローチだが、既存の大規模システムに対しては状態の回避・明示的制御の排除・コードの削減に注力すべき
  • 「タールの沼」から脱出するための銀の弾丸がFRPであるとは断言しないが、答えは間違いなく「簡潔性(simplicity)」である