ソフトウェア設計

【モジュール化 #1】モジュール化とは?コードを分割する本当の理由(凝集度・結合度まとめ)

プロジェクトが大きくなるほど、1つのファイルを修正するのが怖くなります。影響がどこまで広がるか見通せないからです。

読了 5 分
【モジュール化 #1】モジュール化とは?コードを分割する本当の理由(凝集度・結合度まとめ)のカバー画像

プロジェクトが大きくなるほど、1つのファイルを修正するのが怖くなります。影響がどこまで広がるか見通せないからです。

ビルドは遅くなり、コードレビューの範囲は広がります。新しいメンバーはどこから読めばよいか途方に暮れます。

モジュール化は、この問題に対する最も古くからある処方箋です。一言でいえば、大きなコードの塊を、独立して理解・交換できる単位に分けることです。

この記事では、何を分けるのか、良いモジュールの基準である凝集度と結合度、分割するタイミングまで整理します。

まず要点をまとめます。

  1. モジュール化とは、「一緒に変わるもの」をまとめて境界を設ける作業です
  2. 良いモジュールの基準は、高い凝集度と低い結合度です
  3. 目的はビルド速度より、変更コストの削減です
  4. 境界が曖昧なまま分割すると、複雑さだけが増します

モジュール化では何を分けるのか?

フォルダーを分けることと、モジュールを分けることは異なります。

フォルダーは単なるファイル整理です。どのフォルダーのコードからでも、別のフォルダーのコードを自由に使えます。

モジュールには強制力のある境界が生まれます。外部から使えるのは、そのモジュールが公開したインターフェースだけです。

モジュール化の本質はファイル整理ではなく、外部に必要な情報と不要な情報をコンパイラーに強制させることです。

この境界によって、2つのことが可能になります。

  • 独立した理解:内部を知らなくても、インターフェースだけで使える
  • 独立した交換:インターフェースが同じなら、実装全体を替えても外部に影響しない

1972年にDavid Parnasが論文で示したinformation hidingの概念がこれです。分割基準は機能ではなく、「隠したい設計判断」です。


良いモジュールの基準:凝集度と結合度

分割したのに使いにくくなったなら、たいていこの2つの指標が噛み合っていません。

凝集度は、モジュール内のコード同士がどれだけ関連しているかです。高いほど良い指標です。

結合度は、モジュール同士がどれだけ密接に依存しているかです。低いほど良い指標です。

簡単な判定方法があります。

質問 兆候
1つの機能を直すために、複数のモジュールを同時に修正しますか? 結合度が高い
モジュール内に無関係なコードが混在していますか? 凝集度が低い
1つのモジュールを削除すると、その機能だけが消えますか? 適切に分割されている

基準は変更です。一緒に変わるコードは同じモジュールに、別の理由で変わるコードは別のモジュールに置きます。

おなじみの原則ですね。SOLIDの単一責任の原則(SRP)が示す「変更の理由」を、モジュール単位に広げたものです。

モジュール外には公開インターフェースだけを見せる構造
モジュール外には公開インターフェースだけを見せる構造

モジュール化すると何が良いのか?

まず変更コストが下がります。

境界があれば、変更の影響範囲をモジュール内に閉じ込められます。公開インターフェースが変わっていなければ、外部は安全だとレビューできます。

チームでの分業も容易になります。モジュールごとに担当を分ければ、作業領域の衝突が減ります。

テストも軽くなります。モジュール単体でテストできるため、アプリ全体を起動せず検証できます。

ビルドも速くなります。変更したモジュールだけ再コンパイルできるからです。ただしこれは結果であり目的ではありません。境界が悪ければ毎回全体をビルドすることになります。

Swiftでは、境界はアクセス制御子に表れます。

// モジュール外にはプロトコルだけを公開
public protocol PriceFormatter {
    func format(_ amount: Int) -> String
}

// 実装はモジュール内部に隠す
final class KoreanPriceFormatter: PriceFormatter {
    func format(_ amount: Int) -> String { "\(amount)円" }
}

外部ではPriceFormatterだけ知っていれば十分です。実装を変えても、モジュール外のコードを再コンパイルすらしなくてよい状態が理想です。

境界はコードより先にホワイトボードに描きます
境界はコードより先にホワイトボードに描きます

いつ分け、いつ我慢すべきか?

モジュール化は無料ではありません。境界を作ると、インターフェース設計、バージョン管理、プロジェクト設定のコストが発生します。

状況 判断
1つの機能修正で、複数チーム・複数領域のコードが変わる 分ける時期
ビルドが遅く、開発の流れが頻繁に止まる 分ける。ただし変更境界から設計する
ドメイン境界がまだ頻繁に変わる初期プロダクト 待つ。境界が固まってから
1人で作る小さなアプリ フォルダー整理とアクセス制御子で十分

境界を誤ったモジュール化は、しないより悪いものです。2つのモジュールが常に一緒に変わるなら、その境界は間違いです。統合すべきです。

面接ではこう聞かれます

Q. 凝集度と結合度を説明し、両者の関係を述べてください。

凝集度はモジュール内部要素の関連性、結合度はモジュール間の依存の程度です。良い設計は高い凝集度と低い結合度を目指します。関連するコードを集めると境界の外とのやり取りが減るため、両者は補完関係にあります。

Q. モジュールを分ける基準は何にしますか?

変更の理由を基準にします。同じ理由で一緒に変わるコードは同じモジュールに置き、異なる理由で変わるコードは分離します。機能一覧ではなく、そのモジュールが隠す設計判断を説明できて初めて正しい境界になります。


次の記事では、分割後に必ず直面する問題である、モジュール間の依存方向と循環依存を扱います。

分けることより、分けた後の関係設計こそ難しい部分です。凝集度と結合度の感覚を持って読めば、ずっと理解しやすくなります。

あわせて読みたい