プロジェクトが大きくなるほど、1つのファイルを修正するのが怖くなります。影響がどこまで広がるか見通せないからです。
ビルドは遅くなり、コードレビューの範囲は広がります。新しいメンバーはどこから読めばよいか途方に暮れます。
モジュール化は、この問題に対する最も古くからある処方箋です。一言でいえば、大きなコードの塊を、独立して理解・交換できる単位に分けることです。
この記事では、何を分けるのか、良いモジュールの基準である凝集度と結合度、分割するタイミングまで整理します。
まず要点をまとめます。
- モジュール化とは、「一緒に変わるもの」をまとめて境界を設ける作業です
- 良いモジュールの基準は、高い凝集度と低い結合度です
- 目的はビルド速度より、変更コストの削減です
- 境界が曖昧なまま分割すると、複雑さだけが増します
モジュール化では何を分けるのか?
フォルダーを分けることと、モジュールを分けることは異なります。
フォルダーは単なるファイル整理です。どのフォルダーのコードからでも、別のフォルダーのコードを自由に使えます。
モジュールには強制力のある境界が生まれます。外部から使えるのは、そのモジュールが公開したインターフェースだけです。
モジュール化の本質はファイル整理ではなく、外部に必要な情報と不要な情報をコンパイラーに強制させることです。
この境界によって、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. モジュールを分ける基準は何にしますか?
変更の理由を基準にします。同じ理由で一緒に変わるコードは同じモジュールに置き、異なる理由で変わるコードは分離します。機能一覧ではなく、そのモジュールが隠す設計判断を説明できて初めて正しい境界になります。
次の記事では、分割後に必ず直面する問題である、モジュール間の依存方向と循環依存を扱います。
分けることより、分けた後の関係設計こそ難しい部分です。凝集度と結合度の感覚を持って読めば、ずっと理解しやすくなります。

