LoginSignup
0
0

コンポーネントの凝集原則 - 全再利用原則(CRP)

Last updated at Posted at 2023-12-28

背景まえがき

コンポーネントの凝集原則には3つあり、
そのうちの2つは再利用観点優勢での凝集、
残り1つは単一責務、変更容易性観点優勢の【閉鎖性共通原則(CCP)】である。
この再利用観点の2つの原則は、
ソフトウェアアーキテクチャハードパーツに出てくる再利用パターンの設計思想を理解するうえでだけでなく、
再利用領域を担当する部門設計といったチームトポロジー考察にも必須であると感じた。

全再利用原則(CRP)

この原則の考え方は、クリーンアーキテクチャ本で書かれてるものだと以下の通りだ。

--引用--
「そのコンポーネントを使っているユーザー側に対して、実際に使わないものへの依存を強要してはならない。」
--引用終わり--

何をいってる原則か?

何を言ってるかというとたとえば以下の図のようなことを考えてほしい。
今回は簡単のために、ある2つのパッケージで考察してみることにする。

ここでXやY、Sが具象クラスなのか、インターフェイスや抽象クラスのような抽象なのかは本質ではないので、特に考えないことにする。
コンポーネントの凝集原則.png (15.2 kB)

図のようにパッケージa内のクラス達は、パッケージb内の
クラスX、クラスYは使っているが、クラスSに関しては使っていないものとする。
こんな風にパッケージaが一切依存していないようなクラスをパッケージbに混ぜるな
というのが、この原則で言っていることである。

なぜ守るべきなのか?

この時パッケージ中身の詳細なクラスとどの程度依存関係にあろうと、
パッケージ間の依存関係は、a→bであることに変わりはない。
図で捉えるとわかりやすいが、要はあるパッケージaが別のパッケージbに依存している場合、
実際にはそのパッケージb内のすべてのクラスに依存している。
パッケージ図.png (6.7 kB)

パッケージb内のクラスXやYを名前変更したりした際に、
パッケージaの方で再コンパイルしたり、再度デプロイしたりするのが必要なのは仕方がない事だからいいけども、
じゃあクラスSは?
クラスSを変更したことによって、パッケージbは新しいVerへと更新され、
それに伴ってパッケージa側は、再度コンパイルやデプロイの必要性が出てきてしまう。
実際にはクラスSのことなんて使ってもいないのに、だ。

これがまだ図のようにパッケージが2つだったらかわいいもんだけど、
パッケージaを参照する他のパッケージとかがあったらどうだろう?
本来パッケージaから使われていない、クラスSがパッケージbに存在することによって、
不必要で意図しない変更コストが余計にかかってしまう可能性が高い。

そもそもパッケージb内のたった1つのクラスを変更しただけでも、
パッケージa側は再度コンパイルやデプロイする必要性があるのは言うまでもない。
だったらクラスSのようなものはパッケージbに含めるべきではない。

別の例でなぜを確認

他の例でも確認してみよう。
下図のようにクラスX、Y、Zは密に結合している場合を考えてみる。
コンポーネント原則.png (15.3 kB)
この場合には、実質パッケージa内のクラス群は、
クラスXだけではなく、YとZにも間接的に依存しているのがわかる。
なので、YやZはこのパッケージbにまとまっているべきではあるが、
クラスSに関しては、間接的にもパッケージaは使っているわけではないので、
bに含まれるべきではない、ということ。

このように共に再利用したいものと、そうではないものとが混在する状態を避けなさい
というのがこの原則で主張されていること。

どんな状況下で守るべき?

この原則は、実は閉鎖性共通原則(CCP)とのトレードオフが求められる。
常に同時に変更されるクラス群を1つにまとめよっていうのが、閉鎖性共通原則(CCP)であるのに対して、
常に同時に再利用されるクラス群を1つにまとめよっていうのが、全再利用原則(CRP)。

同時に変更されるクラス群が、必ずしも同時に再利用されるとは限らない。
まだそのコンポーネントが開発初期の段階で、不安定なフェーズである場合には、
CRPの観点の考察は不要であるが、ある程度使われて枯れてきた安定フェーズに突入したら、
他のプロダクトでも再利用したくなってくるフェーズが出てくる。
その際にはCCPの観点からの考察だけではなく、徐々にCRP観点が優勢になってくる。
68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f32333130312f30343962386132652d653137382d643739652d326336392d6332663232383932383533662e706e67.png (56.7 kB)

その安定フェーズに差し掛かった際に、メンテ性CCPと全再利用CRPを
バランスよく満たすようにしようとすると、
細かく分割された複数のコンポーネントを配置する必要性もあるため、
別の記事で書くリリース等価原則(REP)の観点も考察する必要性が生じてくる。
もちろん、もう十分に枯れて全然メンテが入らない超安定したコンポーネント
っていう状態なら、CCP観点は無視してこの全再利用原則CRPに振り切ってよい。
そのため、概形にはなるが、上図のような青い軌跡を辿るというイメージ。

なので、一番頭を悩ませるのは、CCPとCRPの両方の観点の考察が必要な時である。
これはサービスのレベルでの再利用パターンでも重要になってくる考え方なので、
マイクロサービスの再利用パターンとセットで学習することをオススメします。

インターフェイス分離原則との関係性

インターフェイス分離原則では、クライアント側が依存するインターフェイス内の処理として、
必要とする処理と必要としない処理が混在している状態を避けなさい
と主張されている。

たとえば下図のようなケースで、
クライアント側のパッケージからは、
・再生
・一時停止
・停止
・早送り
・巻き戻し
は使われているけども、録音処理は使われていないようなケース。
インターフェイス分離.png (14.5 kB)
仮に別のクライアントパッケージからはすべての処理を使われているとしても、
図のように一部のクライアントからは、すべての処理は使われていない、
というケースでは、
たとえば録音処理の事前事後条件が変更されたとか、メソッド名が変更された
といった際に、不必要な変更コストが発生してしまう。
これがインターフェイス分離原則に違反している事例である。

そのため、原則にのっとって以下のように修正することで不要な変更コストを抑えられる。
原則に従った例.png (15.5 kB)
外部のパッケージに公開したいメソッドの集合を設計する際には、必ず意識したい設計原則である。

このインターフェイス分離原則も、コンポーネントの全再利用原則も
どちらも【ともに使われないような不要なものへの依存はしないこと】という思想。
ようは、クラスの設計原則のインターフェイス分離原則を
コンポーネントレベルに一般化したものがこの全再利用原則である。

これは単一責任原則をコンポーネントレベルまで一般化したものが閉鎖性共通原則(CCP)
であるのと同様の理論だと考えられる。
なので、クラスっていう最小単位での設計思想は、よりマクロなレベルでの
パッケージだのサービスの粒度の設計思想にもパクれるってこと。

余談

Javaのライブラリ

Javaのライブラリである、java.utilパッケージやjava.langパッケージを覗いてみると、
両パッケージは相互依存関係ではあるものの、
非常に安定したライブラリであるがゆえか、このCRPを意識したんだろうな~
というパッケージ構成になっているので、参考までに。

戦略Capability

事業戦略を成立させる上で必要な能力のビジネスCapability。
これも運用していく中で「この能力は安定してきていて、他の事業でも再利用したい要素だな」っていう瞬間が出てきます。
詳細はここでは省きますが、システムレイヤーの再利用だけでなく、
ビジネスCapabilityといった事業戦略、経営戦略などの抽象度の上の方のレイヤー部分での
再利用を考える際にもマストな考え方になるので、
経営サイドや事業責任者などの管理職の方々は是非モノにしてみてください。

参考文献

クリーンアーキテクチャ https://amzn.asia/d/333zzVj
プログラマのためのJava設計ベストプラクティス https://amzn.asia/d/52eqkyf

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0