これは「アジャイルソフトウェア開発の奥義」の第4部の第20章のまとめである。
興味ある方は買った見て読んでみることをオススメする。
自分は第一版を買ったが、今なら第二版を買うのが良いだろう。
パッケージ内部の凝集度に関する3つの原則
1. 再利用・リリース等価の原則 (REP : Reuse-Release Equivalency Principle)
再利用の単位とリリースの単位は等価になる
再利用の単位(パッケージ)が、リリースの単位より小さくなることはない。 つまり、再利用されるものはリリース番号を割り当てて、それをトラッキングもされなければならない。
再利用できると主張するだけでは現実的でない。 再利用を再現するには、トラッキングシステムが整備され、それによってそのユーザーが潜在的に必要とする連絡、安全性、サポートを保証できなけばならない。
ソフトウェアが再利用される場合は、その目的を果たすために人間にとって都合の良いように構造化される。 つまり、パッケージの内部は、それを再利用する可能性のある人の視点に立って構造化されなければならない。 したがって、再利用されるソフトウェアを含むパッケージには、再利用されないソフトウェアを入れるべきではない。
ユーザーがパッケージを再利用するとき、必要になるはその一部のクラスだけで、他のクラスは全く役に立たないというのは望ましくない。
2. 全利用の原則 (CRP : Common Reuse Principle)
パッケージに含まれれるクラスは、全て一緒に再利用される。つまり、パッケージに含まれるいずれかのクラスを再利用するということは、その他の全てのクラスも再利用することを意味する
クラスが単独で使われることはほとんどない。
どのクラスを一緒のパッケージに含めるべきか、ということだけでなく、同時にどのクラスを一緒のパッケージに含んでいけないのかも教えてくれる。
互いに強い関係性を持たないクラスを同じパッケージにまとめるべきではないと主張している。
3. 閉鎖性共通の原則 (CCP : Common Closure Principle)
パッケージに含まれるクラスは、みな同じ種類の変更に対して閉じているべきである。 パッケージに影響する変更はパッケージ内の全てのクラスに影響を及ぼすが、他のパッケージには影響しない。
これは、単一責任の原則(SRP)のパッケージ版と言える。
ほとんどのアプリケーションは再利用性より保守性の方が重要とされる。
アプリケーションコードをどうしても変更しなければならないのなら、変更箇所がたくさんのパッケージに またがっているよりも、一つのパッケージにまとまっていて欲しいと願うだろう。 変更箇所が一つのパッケージに集中していれば、再リリースは変更されたパッケージだけで済むからだ。 もちろん、変更されたパッケージに依存していない他のパッケージは再評価や再リリースをする必要がない。
CCPは「オープン・クローズドの原則 (OCP)」と密接な関係がある。OCPの言葉では、クラスの修正に対して閉じ、拡張に対しては開いているべきということである。 しかしながら、完璧な閉鎖を達成するのは不可能であり、したがって閉鎖は戦略的に行わなければならない。つまり、最もありがちな変更に対してシステムが閉じるように設計するのだ。
CCPはこのことをさらに強調し、同じ理由で修正されるクラスは一つのパッケージにまとめるべきだと主張している。 こうすることで、仕様変更があった場合でも、その影響を受けるパッケージの数を最小限にすることができる。
安定性 : パッケージ同士の結合度に関する原則
1. 非循環依存関係の原則 (ADP : Acyclic Dependencies Principle)
パッケージ依存グラフに循環を持ち込んではいけない。
コードを部分的に動くようにして安心したのは束の間、全体では動かなくなっているという経験があるだろう。 規則を守らない開発チームが、安定したバージョンのプロジェクトをビルドすることができずに何週間も時間を浪費してしまうことは珍しくない。 誰もが、誰かが行った変更を使ってコードが動くように変更を重ねるからだ。
この問題に対処する2つの対策方法が考え出された。それが「ウィークリービルド」と「 非循環依存関係の原則 (ADP)」だ。
- ウィークリービルド
中規模のプロジェクトで一般的な手法だ。 週の最初の4日日間は全ての開発者がお互いの存在を無視する。それから、金曜日に全ての変更を統括し、システムをビルドするといったやり方だ。 しかし、これも金曜日に大きな統合作業が必要になることで色々と問題が発生する。詳細はここでは割愛するので、興味ある方は書籍を読んで見てくれ。
- 非循環依存関係の原則 (ADP)
開発環境をリリース可能なパッケージ単位に分割するのだ。非常にシンプルかつ理にかなったプロセスである。 しかしながら、これを実現するには、パッケージ間にサイクル(循環)を生じないように、パッケージの依存構造を管理しなければならない。
下図のパッケージの依存関係構造は、非循環性の有効グラフである。
MyDialogsの作業を担当しているチームが新しいパッケージをリリースすると、影響を受けるのが誰か見つけるのは容易い。依存関係の矢印を逆に辿ればいいだけだ。 今回でいえば、MyTaskとMyApplicationが影響を受けることがわかる。
そのパッケージのテストを走らせければ、作業中にMyDialogsのバージョンを現在利用しているWindowsパッケージだけとコンパイルしてリンクするだけだ。 システム中の他のパッケージは一切関与しない。
では、パッケージ依存循環関係グラフにおける循環が与える影響についても見てみよう。
最初の図にMyDialogs -> MyApplication
の線を追加した。
この循環は即座に問題を生み出す。例えば、MyTaskパッケージの作業をしている開発者はTasks, MyDialogs, Database, Windowsに依存していることを知っている。しかしながら、「循環(サイクル)」があるので、MyApplication, TaskWindows, MessageWindowにも依存していることになる。すなわち、システム中のパッケージの全てに依存してしまうのだ。
つまり、開発者はまたも最初の問題のように時間を浪費してしまうだろう。
しかし、これは問題の一端にすぎない。 MyDialogsのパッケージをテストしたいときに、Databaseを含むシステムの全てのパッケージをリンクしなければならない。 このことはMyDialogsをテストするためにシステム全てをビルドしなければならないことを意味する。
また、依存グラフに循環があると、パッケージをビルドする順番が非常に難しくなる。実際、正しい順番というものがないからだ。
循環を断つ方法
パッケージの循環を断ち切って依存関係グラフを非循環性の有向グラフに作り変えることは必ずできる。 その中心的な役割を担うメカニズムは2つある。
- 依存関係逆転の法則(DIP)を適用する
元々は以下のようになっていた。
MyDialogsが利用するインターフェースを抽象基本クラスとして用意する。その抽象クラスをMyDialogsに持たせ、その派生クラスをMyApplicationの中に置く。こうすることによって、MyDialogsとMyApplicationの依存関係が逆転するので、循環が断ち切れる。
詳しくはSOLID原則の記事を書くつもりなので、しばらくお待ちを(他の記事を参考にしたほうが良いでしょう...)
- MyDialogsとMyApplicationが両方とも依存するようなパッケージを新たに追加し、両方が依存するクラスをこの新しいパッケージに移行する
以下のように aNewPackage を作ることで循環を断ち切れる。
2. 安定依存の原則 (SDP : Stable Dependencies Principle)
安定する方向に依存せよ
この原則を使えば、特定の種類の変更に敏感なパッケージを作ることができる。これらのパッケージは不安定さを前提に設計されており、したがって変更を意識して作られる。
変更することを意識して作られたパッケージが、変更が難しいパッケージに依存するような立場になってはならない。 そうなってしまうと、せっかく変更することを意識して作られたはずのパッケージの変更が難しくなるからだ。
SDPに従えば、変更しやすく設計したモジュールがそれよりも変更が難しいモジュールに依存されないことを保証することができる。
安定性
ここで安定性とは何か考える。
下図のパッケージAは非常に不安定だ。 Aに依存しているパッケージは存在しない。このことを「Aは責任を負わない」という。 また、Aは3つのパッケージに依存している。したがって、外的な要因によってAが変更を強いられる可能性がある。 このことを「Aに依存している」という。
不安定なパッケージA
逆に、下図のHは安定したパッケージだ。このパッケージには3つのパッケージが依存されている。 それゆえに、Hを変更するには真っ当な理由が3つ必要になる。このことを「Hはこれらの3つのパッケージに対して責任を負う」という。 また、このHは全く何にも依存していない。したがって、外的要因によってHが変更を強いられるようなことはない。 このことを「Hは独立している」という。
安定したパッケージH
安定性の尺度
パッケージの安定性を測定する方法の一つは、そのパッケージの出入りする依存関係の数を数えることだ。
C++ではこれらの依存関係は#include文で表現される。Javaではimport文だろう。
不安定度 I は以下のように表せる
I = Ce / (Ca + Ce)
I : 不安定度 Ca : このパッケージの外にあり、このパッケージの中にあるクラスに依存するクラスの数 Ce : このパッケージの中にあり、このパッケージの外にあるクラスに依存するクラスの数
I = 0 が最も安定したパッケージであり、I = 1 が最も不安定なパッケージとなる。
では、実際に下図を用いてパッケージPcの安定性を測定してみよう。
Pcの外にあり、Pcの内部にあるクラスに依存するクラスの数は 3 (q, r, s)である。したがって、Ca = 3 である。 Pcの中にあり、Pcの外にあるクラスに依存するクラスの数は 1 (v)である。したがって、Ce = 1 であり、 I = 1 / 4 となる。
安定依存の原則(SDP) は 「パッケージの不安定度 I は、それが依存するパッケージの不安定度 I の値よりも大きくあるべきだ」と主張している。
違反するものは基本的には、依存関係逆転の法則を使うことで解消できる。 詳細は気が向いたら書こうと思う。
3. 安定度・抽象度等価の原則 (SAP : Stable Abstractions Principle)
パッケージの抽象度と安定度は同程度でなければならない。
この原則が主張していることは、安定度の高いパッケージはその拡張性を失わないためにも抽象的でなればならない。一方、不安定なパッケージは具体的でなければならない。不安定であれば具体的なコードを簡単に変更することができるからだ。
つまり、パッケージを安定にさせる場合は、パッケージは抽象度クラスで構成されるべきである。
安定度・抽象度等価の原則(SAP) と 安定依存の原則(SDP) の2つをまとめると、それはパッケージ版の依存関係逆転の原則(DIP)ということになる。
安定依存の原則(SDP) は依存関係が安定する方向に向かうべきであることを主張し、 安定度・抽象度等価の原則(SAP) は安定するということは抽象化されているということを意味するからだ。つまり、依存関係は抽象度の高い方向に向かうことになる。
まとめ
パッケージ内部の凝集度に関する3つの原則
- 再利用・リリース等価の原則 (REP)
- 全利用の原則 (CRP)
- 閉鎖性共通の原則 (CCP)
安定性 : パッケージ同士の結合度に関する原則
- 非循環依存関係の原則 (ADP)
- 安定依存の原則 (SDP)
- 安定度・抽象度等価の原則 (SAP)
自分は、アジャイルソフトウェア開発の奥義を読んで学習したが、 著者が同じである Clean Architecture の方が新しく有名なのでそっちを買った方がいいかもしれない。
どちらにせよ、パッケージの原則だけでなく、SOLID原則など設計に関する多くのことがどちらの書籍にも書かれているので、エンジニアであるならば一度は読んでおくことをオススメする。