ソフトウェア設計

結合度(Coupling)と凝集度(Cohesion)、ソフトウェア設計の根幹

コードは動くのに、機能を一つ直すだけで、なぜあちこち修正が必要になるのでしょうか。

読了 5 分
結合度(Coupling)と凝集度(Cohesion)、ソフトウェア設計の根幹のカバー画像

コードは動くのに、機能を一つ直すだけで、なぜあちこち修正が必要になるのでしょうか。

ボタンの色を変えただけで決済ロジックが壊れる経験、誰にでも一度はありますよね。

その原因をたった二つの言葉で説明する概念が、結合度と凝集度です。

結合度は低いほどよく、凝集度は高いほどよい。よい設計の大半は、この一文から始まります。

この記事では、二つの概念の正確な意味、設計原則の根幹と呼ばれる理由、実際のコードへの適用方法まで、私の経験を交えて解説します。

まずは要点を整理しておきましょう。

  1. 結合度(Coupling): モジュール同士がどれだけ絡み合っているか → 低いほど よい
  2. 凝集度(Cohesion): モジュール内のコードが一つの仕事にどれだけ集中しているか → 高いほど よい
  3. この二つが、SOLIDやデザインパターンなど、有名な原則の土台です。
  4. 目標は一つ、「直しやすく、差し替えやすいコード」です。

結合度と凝集度とは、一体何でしょうか?

まず、二つの概念を一文でまとめるとこうなります。

結合度はモジュール間の関係を、凝集度は一つのモジュール内部のまとまりを指します。

方向が正反対なので混同しやすいですが、私はこう覚えました。

結合度は「外との距離」、凝集度は「内側のまとまり」です。

結合度が高いと、Aを直すだけでBやCまで次々と影響を受けます。ドミノのように。

凝集度が低いと、一つのクラスに決済ロジック、メール送信、ログ記録が混在します。名前だけでは何をするクラスか分かりません。

よい設計は、この二つを逆方向に進めます。外とは疎結合に、内側は強固に。


なぜこれが設計原則の「根幹」なのでしょうか?

結合度と凝集度は、1970年代の構造化設計理論で初めて整理された概念です。ラリー・コンスタンティンが提唱したとされています。

半世紀近く経っても通用するのは、特定の言語や流行に左右されない本質だからです。

よく知られた原則を掘り下げると、結局ここに行き着きます。

原則・パターン 結局伝えたいこと
単一責任原則(SRP) 凝集度を高める
依存性逆転(DIP) 結合度を下げる
インターフェース分離(ISP) 不要な結合を断つ
大半のデザインパターン 低い結合度 + 高い凝集度

分かりますよね。名前は違っても、根は一つです。

だから新しい原則やパターンを学ぶときは、「結合度を下げるものか、凝集度を高めるものか」とまず考えます。これで半分は理解できます。

私はこうして紙にボックスと矢印を描きながら構造を整理します
私はこうして紙にボックスと矢印を描きながら構造を整理します

結合度を下げる方法、コードで見ると?

言葉だけでは抽象的なので、短い例を見てみましょう。

以下は結合度の高いコードです。注文クラスが特定の決済会社を直接知っています。

class Order {
    let pay = KakaoPay()  // Kakao Payにべったり依存
    func checkout() {
        pay.send()        // 別の決済に変えるならここを大改修
    }
}

決済会社をTossに変える瞬間、Orderを開いて修正する必要があります。これが高い結合度の典型です。

次はプロトコルで間に距離を作ってみます。

class Order {
    let pay: Payment                // '「決済」という契約だけに依存
    init(pay: Payment) { self.pay = pay }
    func checkout() { pay.send() }  // 何が来ても関係ない
}

これでOrderは、どの決済会社が来ても気にしません。差し替えるだけです。

インターフェースを一つ挟むだけで、結合がこれほど緩くなります
インターフェースを一つ挟むだけで、結合がこれほど緩くなります

実務でこう変えてからは、決済会社の追加依頼が来ても既存コードをほとんど触らなくなりました。これが低い結合度の力です。


凝集度を高めるにはどうすればよいでしょうか?

凝集度は、「このモジュールが一つの仕事だけをしているか」で判断します。

私の基準は単純です。クラス名を口にしたとき、その中のメソッドが自然にすべて思い浮かぶなら、凝集度は高いといえます。

たとえばUserServiceに次の処理が混在していたら、危険信号です。

  • 会員登録の処理(本業)
  • マーケティングメールの送信(他人の仕事)
  • CSVレポートの生成(これも他人の仕事)

無関係な仕事が、一つ屋根の下で同居しているようなものです。

この場合、メールはEmailSenderに、レポートはReportGeneratorに、それぞれ部屋を与えます。

そうすれば、メールロジックを直すときにUserServiceを開く必要がなくなります。変更の波及範囲が小さくなるのです。

低い結合度と高い凝集度は、結局同じ方向を目指します。変更を広げないことです。


よくある質問 (Q&A)

Q. 結合度と凝集度、どちらから意識すべきですか?

私は、まず凝集度を整えることを勧めます。一つのモジュールを一つの仕事に集中させて分割すると、自然に結合度も整理されることが多いからです。

Q. 結合度は必ず0がよいのでしょうか?

いいえ。モジュール同士がまったく結び付かなければ、プログラムは動きません。目標はゼロではなく、「必要な分だけ、疎結合に」です。

Q. 最初から完璧に設計すべきですか?

その必要はありません。私もまず動くものを作り、直すのがつらくなるたびに少しずつ結合を断ち、凝集度を高めます。リファクタリングは本来、繰り返すものです。


結合度と凝集度は華やかな新技術ではありませんが、長く使えるコードを書く人に共通する習慣です。

今日書いたコードで「これを直すと、どこまで影響するだろう?」と一度考えてみてください。その問いを重ねると、設計感覚が大きく磨かれます。応援しています!

あわせて読みたい