パッケージ原則がOOPとアーキテクチャを繋ぐ #1~ REP編~
この記事のゴールは 「優れたOOPのコード(点)を、いかにして優れたアーキテクチャ(面)へと配置していくか」 という問いに答えることです。
パッケージ原則は、そのための「区画整理」のルールです。
今回は、パッケージ原則を
1. パッケージ原則の意義~健全なパッケージ(REP)
2. パッケージの凝集度(CCP, CRP)
3. パッケージの結合度(ADP, SDP, ASP)
に分けて、解説していきます。
はじめに:なぜ「パッケージ」を設計するのか?
これまでの振り返り(点と面):
これまで、システムの「最終的な設計図」(クリーンアーキテクチャのレイヤー)を学んで来ました。
更に、その設計図を構成する最小単位の「建材」(OOPのクラスやインターフェース)を学びました。
新たな問い:「区画」の必要性
しかし、「建材」と「設計図」の間には、大きなギャップがあります。
数百、数千に及ぶクラス(建材)を、どうやって論理的な「地区」や「区画」にまとめ上げ、設計図(アーキテクチャ)に配置すれば良いのでしょうか?
この「区画整理」のルールこそが、パッケージ原則です。
パッケージ原則は、OOP(ミクロ)とアーキテクチャ(マクロ)を繋ぐ 「中間レベル」の設計 です。
この章を理解することで、設計スキルは「コード」から「構造」へと飛躍します。
ここで、そもそも、その区画整理の大前提としての、「健全なパッケージ」とは何でしょうか?
パッケージが健全でないと、後に紹介するパッケージの凝集度、パッケージの結合度をいくら考えても無意味です。
健全であって初めて、プログラムの一部として有効にパッケージを活用できるようになります。
実は、多くの開発者が知っているオブジェクト指向原則(SOLID原則)の根底を成しているのは、パッケージ原則です。
このことから、今回は真に「良いコード」を実践で書くために不可欠なパッケージ原則を学んでいきましょう。
REP ~「健全なパッケージ」を定義する~
REP(Reuse/Release Equivalence Principle)- 再利用・リリース等価の原則
まず、この原則で肝心の「再利用」と「リリース」を本質的に理解していることが大前提です。
そこで、「再利用」と「リリース」に対してありがちな誤解を払拭していきましょう。
幻想としての「コードの再利用」
私たちはOOPの章で「コードの再利用」という言葉に触れました。
多くの開発現場で、コードの再利用は「善」とされています。
しかし、この「再利用」を安易に行うと、どうなるでしょうか?
最悪の再利用:コピー&ペースト
プロジェクトAで作った便利な utils.go を、プロジェクトBにコピペしたとします。
ある日、プロジェクトAで utils.go の重大なバグが発見され、修正されました。
しかし、プロジェクトBはその修正を知る由もなく、バグのあるコードを使い続けます。 これは「再利用」ではなく 「負債の複製」 です。
中途半端な再利用:直接のファイル参照
プロジェクトBが、プロジェクトAの utils.go を直接参照(インポート)したとします。
ある日、プロジェクトAの都合で utils.go の関数名や引数が変更されました。
その瞬間、プロジェクトBはコンパイルエラーで動かなくなります。
これこそが、お馴染みの 「密結合」 の正体です。
安易な再利用は、保守性の悪夢を生み出します。
「再利用」と「リリース」を正しく定義する
この「再利用の悪夢」を解決するため、パッケージ原則では、まず2つの言葉を厳密に定義し直します。
「リリース」とは何か?
それは、git push することではありません。
それは、「v1.2.0」のような明確なバージョン番号を付け変更履歴(Change Log)を公開し
「このバージョンは安定しています」と保証(サポート)することです。
「再利用」とは何か?
それは、コピペすることではありません。
それは、go.modファイルに require example.com/utils v1.2.0 と書き
特定の「リリース」に正式に依存することです。
REPの核心:規律と目的
用語が明確になったことで、ようやくこの原則の本質に入ることができます。
この原則を「規律」と」「目的」というように分けて考えることで、より本質的に理解することができます。
Step1: 規律:なぜ「等価」でなければならないのか?
REP:再利用の単位は、リリースの単位と等価でなければならない (The granule of reuse is the granule of release.)
これは、先ほど定義した「再利用」と「リリース」が、常に1対1の関係でなければならない、というルールです。
つまり 「リリースされていないものを、再利用してはいけない」 という、エンジニアリング上の「規律」を定めています。
この規律(契約)を守ることで、開発者は 依存関係からの自由 を手に入れます。
再利用する側(Bさん)の自由:
Bさんは v1.2.0 という リリース(契約) を使っています。
リリースする側(Aさん)が勝手にコードを変更しても、Bさんのコードは v1.2.0 を使い続けるため、突然壊れることはありません。
Bさんは、自分の好きなタイミングで v1.3.0 にアップデートする「自由」を持っています。
リリースする側(Aさん)の自由:
Aさんは、「v1系を使っている人たちを壊さないように」という規律さえ守れば、v2.0.0で大胆な変更(破壊的変更)を自由に行うことができます。
REPが目指すゴール(目的): 「適度な粒度」を作る
さて、私たちは「規律」を守ることで「自由」を手に入れました。
しかし、ただリリースすれば良いというわけではありません。
ここでREPの『等価』という言葉の、より深い意味が重要になります。
粒度が大きすぎる問題
例えば、あなたの会社に、便利な関数を詰め込んだ company-utils という巨大なパッケージが「v1.0.0」としてリリースされたとします。 このパッケージには、StringUtils(文字列操作)と DateUtils(日付操作)の両方が含まれています。
あなたは今、新しいバッチ処理を作っており、StringUtils だけが再利用したい。 しかし、あなたは company-utils@v1.0.0 を go get するしかありません。
ここで問題が発生します。
ある日、DateUtils に重大なバグが見つかり、company-utils は v1.1.0 にアップデートされました。 あなたは DateUtils など一行も使っていないにも関わらず、このアップデートの通知を受け取り、あなたのバッチ処理も v1.1.0 に追従すべきかどうかの判断(と、それに伴う再ビルドや再テスト)を強制されます。
「等価」の本当の意味
これこそが、
ユーザーが再利用したい粒度: StringUtils
リリースの粒度: company-utils パッケージ全体
が「等価」ではない状態です。
ユーザーは StringUtils という「価値」が欲しかっただけなのに、DateUtils を含むパッケージ全体の「責任」まで負わされています。
REPが本当に私たちに求めているのは、「規律」を守った上で 「利用者が欲しい『価値』と、リリースする『価値』が等価になるよう、まとまりの粒度を適切に設計すること」 なのです。

結論:REPがもたらす「次の問い」
REPはパッケージ間の 密結合を防ぎ、安定した依存関係を築くための「大前提」 を教えてくれました。
しかし、REPはまだ 「何を(What)」を一つのパッケージとしてまとめるべきか という「区画整理」の具体的な基準は教えてくれません。
では、その 「再利用に適した粒度」 とは、具体的にどのような基準で決めれば良いのでしょうか?
この「パッケージの凝集度」を決めるための、具体的な「区画整理」のルールこそが、次に学ぶ CRP(共通再利用の原則)とCCP(共通閉鎖の原則) なのです。
Discussion