📝

パッケージ原則がOOPとアーキテクチャを繋ぐ #2~ CRP, CCP編~

に公開

はじめに ~パッケージングの基準となる2つの原則~

前回の記事では、パッケージ原則の意義と、全てのパッケージ原則の前提知識となるREPについて深掘りしていきました。

https://zenn.dev/hashidev/articles/2fec867ff70d62

そして、REPによって「パッケージは、再利用に適したサイズでリリースされるべき単位」であると分かりました。

では、その適したサイズというのは、何を基準にまとめていけば良いのでしょうか?

このまとめ方には、「パッケージを利用する側」と「メンテする側」のそれぞれの異なる2つ視点から見た強力な原則が存在します。

それがCRP,CCPです。

【CRP】共通再利用の原則 〜「使う人」のためのルール〜

REPからのバトンパス

前回のREPで、私たちは「パッケージとは、責任を持ってリリースされるべき『再利用』の単位である」と学びました。

そして、その「粒度」が重要であるという、次の問いに直面しました。

CRPはその「粒度」を決定するための、「使う人(再利用者)」の視点に立った最初の原則です。

CRPの原則:「欲しいもの」だけをパッケージに

原則:共に再利用されることが多いクラスだけを、同じパッケージにまとめよ。 (Common Reuse Principle)

この原則が解決しようとする「痛み」は、あなたも開発現場で一度は感じたことがあるはずの 「Aの部分を採用したいが、Bの部分は採用したくない」 というジレンマです。

CRPに違反した「最悪のパッケージ」

この「痛み」を具体的な例で見てみましょう。

ここに社内の便利な関数をすべて詰め込んだ、巨大なcompany-utilsというパッケージがあるとします。

  • company-utilsパッケージ(v1.0.0)
    • DatabaseUtils.go (データベース関連の便利関数)
    • WebUtils.go (Web/HTTP関連の便利関数)
    • StringUtils.go (文字列操作)

あなたは今、シンプルなバッチ処理を作りたいので、DatabaseUtilsだけが欲しい。

しかし、あなたはこの巨大なcompany-utils全体をimportするしかありません。

これが「痛み」の始まりです。

ある日、WebUtilsにバグが見つかり、company-utilsはv1.1.0にアップデートされました。

あなたはWeb機能を一切使っていないにも関わらず、この変更の通知を受け、不要な依存関係のアップデートと、それに伴う再テストを強制されます。

CRPが導く「クリーンな解決策」

CRPはこの設計は間違っていると主張します。

DatabaseUtilsとWebUtilsは、「共に再利用される」とは限らないからです。

CRPに従うならば、パッケージは以下のように分離すべきです。

  • db-utilsパッケージ (v1.0.0)
  • web-utilsパッケージ (v1.0.0)
  • string-utilsパッケージ (v1.0.0)

この設計により、あなたはdb-utilsだけを再利用し、web-utilsの変更から完全に解放されます。

これこそが、CRPが目指す 「交換部品による影響範囲の最小化(ダメージコントロール)」 であり、「パッケージの責務はたった1つだけ」という状態です。

次の「問い」へ:CCPとの「緊張関係」

CRPは「使う人のために、パッケージは小さく分離しろ」と強く主張します。

しかし、ここで新たな疑問が生まれます。

「もしPetインターフェースと、Dog、Catという複数のクラスがあったとする。CRPに従ってこれらもバラバラに分離すべきなのか?それは 保守する側(メンテする人) にとって地獄ではないか?」

この「使う側(CRP)の都合」と「メンテする側の都合」の間に生じる緊張関係。
この後者の「メンテする側」の原則こそが、次に学ぶCCP(共通閉鎖の原則)なのです。

【CCP】共通閉鎖の原則 〜「メンテする側」のためのルール〜

CRPが「使う人」の視点だったのに対し、CCPは 「保守する人(メンテする側)」 の視点に立った原則です。

メンテする側の「痛み」

メンテする側が最も避けたいのは、「たった1つの変更が、システム全体に飛び火すること」です。

開発現場では、このような「痛み」が日常的に発生します。

「petパッケージのPetインターフェースにGroom()(毛繕い)メソッドを追加した。
すると、まったく別のパッケージにあったdogパッケージとcatパッケージが、一斉にコンパイルエラーを起こした!」

これはPet、Dog、Catが 「ペットの振る舞いが変わる」という同じ理由で変更される にも関わらず、物理的にバラバラのパッケージに配置されてしまっている「低凝集」な状態です。

CCPによる「解決策」

この「痛み」を解決するのが、CCP(共通閉鎖の原則)です。

原則:同じ理由で共に変更されるクラスは、同じパッケージにまとめよ(The Common Closure Principle)

CCPは、変更が起きた時に「閉じて(Close)いる」ことを目指します。

「閉じている」とは 「その変更が、パッケージの境界の外に漏れ出さない」 という意味です。

CCPに従うならば、Petインターフェース、Dogクラス、Catクラスは すべて同じ pets パッケージに「まとめる」 のが合理的です。

CCPがもたらす「メリット」

この原則を守れば、PetインターフェースにGroom()メソッドを追加したくなっても、修正が必要なファイルは全て、そのpetsパッケージ内に収まっています。

他のパッケージに影響が波及する心配はなく、開発者は安心して1つのパッケージだけを修正し、テストし、リリースすることができます。

これこそが、CCPがもたらす「保守性の向上」です。

CCPとCRPの「健全な緊張関係」

お気づきでしょうか?

  • CRPは「分けるべき」と言う。(再利用性のために)
  • CCPは「まとめろ」と言う。(保守性のために)

これこそが、パッケージ設計の核心にある「健全なトレードオフ」です。

優れた設計者は、この2つの原則の「緊張関係」を理解しています。

「変更の保守性」と「再利用の独立性」を天秤にかけ、「このプロジェクトの文脈では、どこで線を引くのが最も合理的か?」を常に考えるのです。

また、その判断の土台となるのが、「そもそも、これはリリースする単位として適切か?」という、一番初めに学んだ REP(再利用・リリース等価の原則) であることも併せて把握しておくのが良いですね。

Discussion