開発プロセス●●○○○

結合度と凝集度

けつごうどとぎょうしゅうど

モジュール間の依存の強さと、モジュール内部のまとまりの良さ。設計品質を測る古典的な物差し。

概要

結合度(coupling)は「モジュール同士がどれだけ強く依存し合っているか」、凝集度(cohesion)は「1つのモジュールの中身がどれだけ1つの目的にまとまっているか」を表す言葉です。ここでいうモジュールとは、関数・クラス・パッケージ・サービスなど、プログラムを分割する単位すべてを指します。良い設計の目標は昔から一貫していて、「疎結合・高凝集」— つまりモジュール間のつながりは細く、モジュール内部のまとまりは濃く、です。

この2つは半世紀にわたって使われ続けている設計品質の物差しで、コードレビューで「ここ密結合になってない?」と言うときも、マイクロサービスの分割単位を議論するときも、同じ概念が土台になっています。特定の言語やパラダイムに縛られない、ソフトウェア設計のもっとも基礎的な語彙のひとつです。

大事なのは、これが「変更のしやすさ」を測る物差しだという点です。結合が密なら、1か所を直したときの影響が他のモジュールへ波及します。凝集が低ければ、1つの修正のためにモジュール内の無関係なコードまで読み解く必要があります。どちらも、ソフトウェアを育て続けるコストに直結します。

なぜ生まれたか

この概念が言語化されたのは1970年代、Larry Constantine と Edward Yourdon らによる「構造化設計(Structured Design)」の中でです。当時、プログラムはどんどん大規模化していましたが、分割の仕方は各人の勘に任されていました。goto と共有データが絡み合った、いわゆるスパゲッティコードでは、1行の修正がどこに波及するか誰にも予測できず、修正のたびに別の場所が壊れる — 保守コストが開発コストを圧倒し始めていた時代です。

構造化設計は「プログラムをどうモジュールに分割すれば保守しやすいか」に初めて体系的な答えを与えようとし、その評価軸として結合度と凝集度を定義しました。「分割すればよい」のではなく「分割の良し悪しを測れる」ようにしたことが発明です。以来、オブジェクト指向、マイクロサービス、サーバレスとアーキテクチャの流行が移り変わっても、この物差し自体は一度も古びていません。分割の単位が関数からクラスへ、クラスからサービスへとスケールしただけです。

詳細

結合度 — 依存の「強さ」に段階がある

結合度は単なる「依存の有無」ではなく、依存の質に段階があります。構造化設計では強い順に、相手の内部実装を直接いじる「内容結合」、グローバル変数などの共有データ経由でつながる「共通結合」、フラグを渡して相手の動きを制御する「制御結合」、必要なデータだけを引数で渡す「データ結合」などが定義されました。現代のコードに翻訳すると、「相手クラスのprivateな事情を知っている」「同じ可変状態を共有している」ほど密で、「明示的なインターフェースと必要最小限のデータだけでやり取りする」ほど疎、ということです。

依存はゼロにはできません。まったく依存し合わないモジュールは、そもそも協調して仕事ができないからです。目指すのは依存の削減ではなく、依存を「細く・明示的に・安定した境界に向ける」ことです。

凝集度 — まとまりの「純度」にも段階がある

凝集度にも段階があります。もっとも低いのは、たまたま同じ場所に置かれただけの「偶発的凝集」— 何でも入りの Utils クラスが典型です。中間には「同じタイミングで実行されるから」「同じデータを扱うから」といった理由でまとめられた段階があり、もっとも高いのが、モジュール全体が1つの明確な目的のためだけに存在する「機能的凝集」です。

凝集度が低いモジュールは、変更理由の異なるコードが同居しているモジュールでもあります。決済ロジックとメール文面生成が同じクラスにあれば、メールの文言変更のたびに決済コードを巻き込んだテストとリリースが必要になります。「このモジュールが変更される理由は1つに絞れているか」という問いは、後述する SOLID原則の単一責任の原則へそのまま受け継がれました。

変更の波及範囲という見方

結合度と凝集度は独立した2軸ではなく、「変更がどこまで波及するか」という1つの関心を、モジュールの外側と内側から見たものです。密結合・低凝集なシステムでは、依存の網の目を伝って変更が全体へ広がります。疎結合・高凝集なシステムでは、変更は境界の内側で止まります。

密結合・低凝集疎結合・高凝集注文+通知変更決済+帳票在庫+認証共有データ依存が網の目状に絡み、1か所の変更が全体へ波及する注文変更決済在庫通知明示的なインターフェース細い境界越しの依存だけ。変更は箱の内側に留まる
変更の波及範囲の比較 — 疎結合・高凝集な設計では変更が境界の内側に留まる

疎結合にするための道具立て

疎結合を実現する手段は、規模ごとに用意されています。クラス単位では、具体的な実装ではなくインターフェース(抽象)に依存させ、実物は依存性注入で外から渡します。プロセスやサービスの単位では、内部実装を隠して API だけを公開し、さらにメッセージキューを挟んで非同期にすれば、相手の稼働状況からも切り離せます。デザインパターンの多く(Observer、Adapter、Facade など)も、本質的には結合を細くするための定石です。

落とし穴 — 疎結合は無料ではない

注意したいのは、「分割すれば疎結合になる」わけではないことです。モノリスを安易にサービス分割した結果、密結合のままネットワーク越しに切り離された「分散モノリス」— 1つの機能変更のために複数サービスの同時リリースが必要な状態 — は、この失敗の典型です。物理的な分割は結合を切るのではなく、結合のコストをネットワーク障害や運用複雑性という形で高くつくものに変えてしまいます。分割の前に、まず論理的な境界(何と何を一緒に変更するか)を見極めることが先です。

また、疎結合の追求にも限度があります。抽象層を挟むほど間接参照が増え、コードを追う手間は増えます。結合度と凝集度は「常に最大化・最小化する目標」ではなく、変更の見込みに応じて投資配分を決めるための物差しです。この物差しを個々のクラス設計の指針に落とし込んだものが SOLID原則であり、悪化した結合・凝集を継続的に直していく営みがリファクタリング、放置した結果積み上がるコストが技術的負債です。