【システム理論】DIP(依存関係逆転の原則)から考えるシステムの生存戦略
はじめに
ソフトウェア設計を学ぶと必ず出会う依存関係逆転の原則(DIP: Dependency Inversion Principle)。
教科書的な定義は「上位モジュールは下位モジュールに依存してはならない。両者は抽象に依存するべきである」というものですが、これだけでは少し抽象的でイメージしづらいかもしれません。
そこで、Web開発でよくある「注文時の通知機能」を例に考えてみましょう。
なぜ依存が問題なのか
あるECサイトの「注文確定処理」を作るとします。最初は「注文確定メールを送る」機能だけが必要でした。しかしその後、「LINEでも通知したい」「Slackにも通知を投げたい」と要望が増えたらどうなるでしょうか?
もし、注文処理クラスの中に「メール送信処理」などの具体的なツール(詳細)を直接書いてしまっていたら、通知手段が増えるたびに、本来触る必要のない「注文処理」のコードを修正しなければなりません。これはバグを生む原因になります。
逆転が生むメリット
ここでDIPの出番です。「注文処理(上位の方針)」が「メールやLINE(下位の詳細)」に直接依存するのではなく、間に「通知するという振る舞い(インターフェース)」を挟みます。
こうすることで、上位の「注文処理」は「とにかく通知インターフェースを呼べばいい」ことだけを知っていればよく、その裏側で実際にメールが飛ぶのかLINEが飛ぶのかを気にする必要がなくなります。
これを図解すると、依存の矢印が実装側からインターフェース側へ向くようになり、「依存関係が制御の流れとは逆方向(逆転)」になっていることがわかります。
ただし、この構造を作る最大の目的は、ロバート・C・マーチンが『クリーンアーキテクチャ』等で強調している通り、「上位の方針(Policy)が下位の詳細(Detail)に影響されないレイヤー構造を作ること」にあります。
上位(抽象)は安定し、下位(具体)は変化する——この構造こそが、変化に強いシステムの基盤です。
実はこの構造を、サイバネティクス学者グレゴリー・ベイトソンの『精神の生態学』における論理階型(Logical Types)の視点で読み解くと、DIPが単なる設計指針ではなく、環境変化に動的に対応するためのシステムの生存戦略であることが見えてきます。
そして、その視点を持つことで、なぜ我々の設計がうまくいかないのか——そのよくある原因である組織とコードの衝突の正体にたどり着くことができます。
本稿では、DIPをシステム論的アプローチで再解釈し、最終的にソフトウェアの境界と組織論の結合について論じます。
1. 階層的な論理階型:安定と変化の弁証法
ベイトソンは、学習や進化をコンテクスト(文脈)の階層構造として捉えました。これは単純な二層構造ではなく、下位の多様な試行錯誤(プロセス)が上位のパターンを形成し、上位の構造が下位の振る舞いを制約するという、再帰的なシステムです。
学習の三階層
ベイトソンが提唱した学習理論は、以下の三階層から成ります:
- 学習Ⅰ(Learning I): 刺激と反応の関係を学ぶ(条件反射)
- 学習Ⅱ(Learning II): 「学習の仕方」を学ぶ(学習のコンテクストを理解する)
- 学習Ⅲ(Learning III): 学習のコンテクスト自体を変える(パラダイムシフト)
この構造をソフトウェア設計に当てはめると、以下のような対応が見えてきます:
「上位の固定」が「下位の自由」を保証する
ここで重要なのは、上位(パターン)が厳格に固定されているからこそ、下位(プロセス)の自由な試行錯誤が可能になるという逆説です。
例えば、Level 2 の 抽象IFが厳格に固定されているからこそ:
- Level 1 で「Stripe決済」を追加できる
- 「テスト用のモック」に差し替えられる
- 「銀行振込」への切り替えも容易
このような多様な試行錯誤が、システム全体を破壊することなく安全に行えます。
DIPとは、単なる依存方向の逆転ではなく、「下位のプロセス(詳細)を自由に遊ばせるために、上位のパターン(抽象)を固定する」という、自由のための拘束とも考えられます。
2. システムとサブシステム:隣り合う2つの「精神」
では、このソフトウェアシステムの外側には何があるのでしょうか?
もちろん「組織」ですが、これを単なる「外枠」として固定的に捉えると本質を見誤ります。システム論的に言えば、「ソフトウェア」も一つのシステムなら、「組織」もまた一つのシステムです。
この2つのシステムが**「事業活動」という共通のフィールドの中で、互いに影響を与え合っている**と捉えてみましょう。
技術システムが変化(マイクロサービス化など)すれば、組織システムも変化を求められます。逆もまた然りです。
この2つのシステムのリズムや境界線が不整合を起こした時、システム全体に深刻な病理が発生します。
3. ダブルバインドの現場:デスマーチが生む時限爆弾
私は、設計が腐敗する(スパゲッティコード化する)本質的な原因の全責任を、エンジニアのスキル不足に帰結させるべきではないと思います。
例えば上記の2つのシステム間で論理的整合性のない矛盾した命令が発せられ、エンジニアがその板挟みになるケースがあるからです。
ダブルバインド理論とは
ベイトソンが提唱したダブルバインドとは、メッセージとメタメッセージが矛盾するコミュニケーション状況を指します。
最もわかりやすい例として、炎上中のSIer案件デスマーチにおける構造的な矛盾を見てみましょう。
矛盾する2つの命令(アクセルとブレーキ)
シナリオ
現場のエンジニアに対し、上司や顧客から以下のような指示が同時に飛んでくる状況
レベル1:変更の強制(アクセル)
「仕様変更だ。この画面に新しい割引ロジックを追加しろ。納期は明日だ、遅延は許されない。」
レベル2:変更の禁止(ブレーキ)
「ただし、既存機能には一切影響を与えるな。デグレ(退行バグ)は絶対に許さない。
テスト工数もリファクタリング工数もない。」
エンジニアはここで、「構造を変えろ(機能追加)」と「構造に触れるな(品質担保)」という、逃げ場のない二重拘束に直面します。
病理的解決:論理階型の取り違え
適切なソフトウェア設計をするなら、共通部分を抽象化して責務を分離すべきです。しかし、それは「既存コードへの変更」を伴うため、レベル2の命令(デグレ禁止)に抵触するリスクがあります。
この矛盾状態において、エンジニアは無意識のうちに「論理階型の取り違え」による解決策を選択します:
「既存コードを触ると怒られるなら、 既存コードをコピペして『別物』を作ればいい」
-
OrderManagerクラスをリファクタリングするのではなく、ファイルごと複製してOrderManagerV2を作る - 共通関数を修正するのではなく、その場限りの
if (flag == true)を何重にもネストさせる - 例外処理を追加するのではなく、エラーを握りつぶす
エンジニアは「コピペ」によって、短期的には矛盾した命令を両方クリアします。しかし、ここで失われるものこそが、「上位の契約による、下位の自由の保証」です。
本来、抽象クラスやインターフェースといった「契約」は、「これを満たせば、裏側で自由に振る舞ってよい」という、変更への許可証として機能します。
ところが、ダブルバインド状況下では、この「許可のメカニズム」が機能不全に陥ります。正当な手続き(契約の拡張)による自由が奪われた結果、現場は「脱法的な自由(コピペや密結合)」に走らざるを得なくなります。
これはベイトソンが指摘した、「逃げ場のない矛盾した環境が、適応のための病的な振る舞いを生み出す」という構造(ダブルバインド)そのものです。システムを守るはずの契約が、システムを破壊する足かせへと変貌してしまうのです。
4. 進化と編み込み:Weavingの失敗
上記は極端な例ですが、より一般的な開発現場でも、ソフトウェアの進化(分化)に組織が追従できないことで、同様の不整合が起こります。
これを「編み込み(Weaving)の失敗」として3段階で見てみましょう。
Stage 1: 原始時代
初期フェーズ。アプリはモノリスで、チームも1つ。 ソフトウェアの境界(縦糸)も組織の境界(横糸)も一本であるため、摩擦はありません。 この段階では問題は顕在化しません。
Stage 2: 軋み
機能が増大し、コード上では「注文」「会員」「配送」といったドメイン境界が必要になります。 しかし、組織はまだ「開発部」という大きな括りのまま。誰がどのドメインの責任を持つかが曖昧になります。 この段階で、コードとチームの境界線のズレが始まります。
Stage 3: 硬直と崩壊
コードをマイクロサービスや明確なコンテキスト境界(DDD)で分割しようとします。
しかし、組織が「職能別(フロントエンド課 / サーバーサイド課 / インフラ課)」のような、技術的レイヤー(横の壁)で固定化されている場合、どうなるでしょうか?
この場合、縦(ドメイン)に割りたい技術の意思が、横(職能)に割れた組織の壁に阻まれます。
結果、コンウェイの法則が示す通り、『組織構造への依存(組織の形にコードが似てしまう)』が発生する可能性が高くなります:
- 「インフラ申請が面倒だから、既存の巨大なコンテナにロジックを詰め込む」
- 「他チームとの調整が発生するなら、自チーム内で完結させよう」
これは前章の「時限爆弾」と同じ構造の、組織レベルでのダブルバインドです。
おわりに:全体性(Wholeness)における構造設計
アーキテクチャ設計とは、単にコードの構造を決めることではありません。それは、市場、組織、そしてソフトウェアという、性質の異なる複数のサブシステムが絡み合う「全体(Wholeness)」の中で、変化し続けるための構造を見出す行為です。
「不変」が「可変」を支える
なぜなら、どのようなシステムであれ、「自由な振る舞い」は「強固な制約」の上でしか成立しないからです。それはスポーツやゲームにおいて、制限されたルールがあるからこそ、その中で多種多様なプレーや戦略が生み出されるのと同じです。
この構造は、システム全体に相似形として現れます。
-
市場における構造: 「顧客への価値提供」という目的(上位)が固定されているからこそ、事業戦略(下位)は柔軟に変更できる。
-
組織における構造: 「チームの責務と境界」という権限(上位)が保証されているからこそ、現場の判断(下位)は自律的になれる。
-
ソフトウェアにおける構造: 「ドメインのルール」という抽象(上位)が安定しているからこそ、実装技術(下位)は柔軟に分化できる。
コードがスパゲッティ化し、組織が硬直している時、そこには共通の病理が潜んでいます。それは「守るべき制約(上位)」が揺らいでいるために、本来自由であるべき「現場の試行錯誤(下位)」が、過剰な調整や依存によって封殺されている状態です。
アーキテクトの役割
不確実な市場の中で、システムを健全に成長させるためには、この「上位と下位の関係」を、コードだけでなく組織構造まで含めて整合させる必要があります。
もし、組織の構造が現場の技術的な進化を許容できない形になっているなら、コード上の工夫だけでそれを解決することは不可能です。逆もまた然りで、技術的な境界が曖昧なままでは、組織の自律性も育ちません。
アーキテクトの役割とは、流動的な現実の中に「ここだけは動かさない」という杭(抽象・境界)を打ち込み、環境の変化に合わせてその位置を再定義し続けることだと考えています。
その杭が適切であれば、システムは複雑さを増しても崩壊することなく、環境の変化に合わせてその内部構造をしなやかに変化させ続けることができるでしょう。
参考文献
- グレゴリー・ベイトソン『精神と自然』『精神の生態学へ』
- ロバート・C・マーチン『クリーンアーキテクチャ』『アジャイルソフトウェア開発の奥義』
- エリック・エヴァンス『ドメイン駆動設計』
Discussion