🗂

パッケージ原則がOOPとアーキテクチャを繋ぐ #1~ REP編~

に公開

この記事のゴールは 「優れたOOPのコード(点)を、いかにして優れたアーキテクチャ(面)へと配置していくか」 という問いに答えることです。

パッケージ原則は、そのための「区画整理」のルールです。

今回は、パッケージ原則を
1. パッケージ原則の意義~健全なパッケージ(REP)
2. パッケージの凝集度(CCP, CRP)
3. パッケージの結合度(ADP, SDP, ASP)

に分けて、解説していきます。

はじめに:なぜ「パッケージ」を設計するのか?

これまでの振り返り(点と面):

これまで、システムの「最終的な設計図」(クリーンアーキテクチャのレイヤー)を学んで来ました。
https://zenn.dev/hashidev/articles/d5b3718b9c4041

更に、その設計図を構成する最小単位の「建材」(OOPのクラスやインターフェース)を学びました。
https://zenn.dev/hashidev/articles/ada492742d55ae

新たな問い:「区画」の必要性

しかし、「建材」と「設計図」の間には、大きなギャップがあります。

数百、数千に及ぶクラス(建材)を、どうやって論理的な「地区」や「区画」にまとめ上げ、設計図(アーキテクチャ)に配置すれば良いのでしょうか?

この「区画整理」のルールこそが、パッケージ原則です。

パッケージ原則は、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