|
||||
先日、ある人から次のような質問を受けた。 クラスが多くなったので、パッケージ分割をしたい。 パッケージ分割をする時の、指針を教えて欲しい。 話を聞いてみると、1つパッケージが大きくなってしまい管理が煩雑になって しまったため、今更ながら一旦整理をする決心がついたという。いわばパッケ ージ分割のリファクタリングを行おうというわけだ。 さて、ここで読者の皆さんに問題である。パッケージ分割の「観点」(指針) をできるだけ多く出してみてほしい。あなたなら、どんな観点で分割するだろ うか?可能なら数分時間を取って、自分の観点をできるだけたくさん書き出し てから次に読み進めて欲しい。 * * * 以下が、私が現時点で持っている観点である。1つ1つ説明しながら見ていく。 解説には、設計原則との関わりについて考察を加え、さらに、私が感じている ことを書いてみた。 (1) 名前クラス名を意味的に近いもの同士でパッケージングする。これは最も自然な考 え方だと思う。 クラスの名前に注目してもよい。クラス名を並べて、分類学的な思考を使うこ とになる。これとこれは仲間、これは仲間はずれ、これはよく似てる、これは 違う、という思考である。この観点で分割されたパッケージは、クラスを探し やすい、という利点がある。「このクラス、どのパッケージにあったかな?」 という場面で、クラスからそれが属するパッケージが連想しやすい。この観点 は非常に重要だ。ただし、私の経験では、この観点のみではソフトウェアはよ い構造を生まない。 (2) 粒度パッケージの中にいくつのクラスがあるか?という観点である。具体的には、 1パッケージの大きさを、クラス数で5~50にする(この数は恣意的である)。 例えば、意味的にほとんど同じようなクラス群を分割するのであれば、機械的 にpackage1~package9というように区切ることも可能だ。この観点は、一見、 根拠が弱いような印象を与えるかもしれない。しかし、現代的なオブジェクト 指向においては、「粒度」という観点はますます重要になっている。計測した り、管理したり、認識したりする単位の粒の大きさがそろっていることの効果 を、軽視するべきではない。もともとのパッケージ分割の動機が、「大きすぎ るパッケージ」にあるのであれば、純粋にこの観点のみで分割する、という手 もアリだ。 しかし、私はこの観点を二次的に考えている。つまり、他の観点でうまく分割 し、その後で、この「粒度」をチェックする。もし粒度が大きいならば再度分 割を試みるし、小さすぎるならば、パッケージを統合してもよい。例えば、論 理的にパッケージ分割をした結果、クラスが1つだけのパッケージができたと しよう。私はそのパッケージを、他のパッケージに統合してしまうだろう(そ のパッケージが今後、大きくなるような予想が立つのであれば別である)。 (3) 依存関係分割後のパッケージ依存関係に注目する。双方向依存や循環依存が発生したら、 そのパッケージ分割はよくない。分割後のパッケージ依存関係は、一方向に整 列すべきである。もし、他の観点と、この観点が競合した場合、強制的に依存 関係を逆転させることができる。JavaやC#、C++では、オブザーバー(Observer) やリスナー (Listener)と呼ばれるデザインパターン、さらにC#では、delegate、 C言語では、関数ポインターという機構を使うことによって、パッケージの依存 関係を逆転させることが可能である。 この観点をサポートするオブジェクト指向設計原則に、 ADP(Acyclic Dependencies Principle)(*1) DIP(Dependency Inversion Principle)(*2) ISP(Interface Segregation Principle)(*3) がある。 私は、パッケージ分割後に、この観点を必ずチェックするようにしている。そ れは、(5)で述べる再利用性に強く影響するからだ。 (4) 変更可能性クラスの変更可能性(あるいは逆の指標であるクラスの安定性)に着目し、高 いものと低いものを分割するようにパッケージを分ける。 この視点は、ソフトウェアの「アーキテクチャ連続性」を高めるという点で重 要である。パッケージの変更周波数(変更頻度)は、その中の最も変更周波数 の高いクラスと同じである。変更周波数が違うものが同じパッケージに混ざる のはよくない。できれば、全体の変更周波数の成分を特定して、その成分ごと にパッケージ化していく。そして、依存性の方向を、変更周波数の高い方から 低い方に向かうようにそろえる。これによって、ソフトウェアの変更伝播をで きるだけ避けることができる。例えば、よくMVCというアーキテクチャを採用 することがあるが、これは、変更周波数の低いもの(M=Model)と、変更周波数 の高いもの(V=View)を分割し、それらを中程度のもの(C=Controller)で接続す る、というパターンだ、と言う事もできる。 この観点をサポートする設計原則に、SDP(Stable Dependencies Principle)(*4)、 SAP(Stable Abstractions Principle)(*5)がある。 実は、私は、この観点を最も重視している。 (5) 再利用性変更可能性とよく似た観点として、再利用性をあげよう。もし、他のプロジェ クトや他のサブシステムでも再利用できそうな部品が混じっている場合、それ を個別にパッケージとして取り出す。パッケージとして取り出すことで、再利 用性が促進される(パッケージとして分離しないと、再利用されない)。この 観点をサポートする設計原則に、CRP(Common Reuse Principle)(*6)、 REP(Release-Reuse Equivalency Principle)(*7)、がある。 私は、最近、ナイーブな意味での「再利用性」を信じていない。特にプロジェ クトをまたぐ再利用は、企業の組織的なサポートがない限り難しい。「プロダ クトライン」などの、製品ファミリー全体に渡っての計画的な再利用手法が必 須なのだ。 それよりも、(4)の変更可能性に注目する方が、よりシンプルで効果がある。 XPなども、再利用性よりも変更可能性に積極的に注目する。「再利用」という 将来のできるかどうかわからないものに先行投資するよりも、現プロジェクト の「変化を抱擁」する方に力点を置く。 (6) 凝集度・結合度Myers、Constantine や Code、Yordon、らが80年代後半から90年代前半に、構 造化設計の文脈で提示した、ソフトウェアのモジュール分割の指針である。 詳しくは他にゆずるが、結合度を弱く、凝集度を高く、という指針は、クラス 分割の指針でもあり、パッケージ分割の指針でもある。 凝集度と結合度は、そのレベルも詳しく定義されているのだが、オブジェクト 指向の文脈でのパッケージ分割の指針としては、少し具体性に欠けると私は感 じている。そこで、この方針による、2つのガイドラインを提示してみた。 (6-1) クラス間の関係に注目クラス図を描いてみる。そして、そのクラス図には、関連、継承、依存などの クラス間関係をすべて書き込む。こうすると、どのクラス同士が強い関係を持 っているか、が見えてくる。その強い関係のクラス群を囲う線を描く。当然、 その境界線をまたぐ関係線があるが、その線ができるだけ少なくなるようにす る。この境界線がパッケージの候補である。ここで、パッケージ内の関係線は 凝集度に、パッケージ間の関係線は結合度に対応する。クラスをパッケージ間 で移動して調整しながら、凝集度を高く、結合度を低くしていく。 (6-2) 変更のソースに注目凝集度のより現代的な定義として「同一の変更理由」という解釈がある。パッ ケージ分割においては、「変更のソースを特定し、同種の変更を閉 じ込める」 ように、パッケージを区切るという考え方である。例えば、「データベースス キーマ」という変更のソースに関して、1パッケージで変更が食い止められる ように、「セキュリティポリシ」という変更ソースに関して、1パッケージで 変更が食い止められるようにパッケージを分割するのがよい。これは、 CCP(Common Closure Principle)(*8)と呼ばれている原則だ。 これは、クラスに対する「1つの責務原則」SRP(Single Responsibility Principle)(*9)のパッケージ版とも捕らえられる。さらに、実は(4)の「変更 可能性」で上げた「変更周波数の特定」と、この「変更ソースの特定」は同じ ものであり、私がもっとも重要視している観点である。 * * * さて、以上が私の持ち札すべてなのだが、どうだろうか。みなさんの中には、 もっと違う観点をお持ちの方も多くおられると思う。他の観点をお持ちの方は、 ぜひ、editors at ObjectClub.jpまでご連絡願いたい。 今後議論していきたいと思っている。 ちなみに、このコラムでは、多くの設計原則を名前のみ紹介した。これらは、 Robert C. Martin の"Agile Software Development"(*10)から引用している。 以下に、簡単な日本語訳とともに紹介しておく。また、これらの原則の幾つか は既に紹介済みである。残りは、これからの記事で順に紹介していこうと考え ている。 [1] ADP(Acyclic Dependencies Principle)パッケージは循環依存してはならない。 [2] DIP(Dependency Inversion Principle)抽象は詳細に依存してはならない。 [3] ISP(Interface Segregation Principle) インターフェイスは最小であるべき。 [4] SDP(Stable Dependencies Principle)パッケージの依存方向は安定性の方向と一致すべき。 [5] SAP(Stable Abstractions Principle)安定したパッケージは抽象的であるべき。 [6] CRP(Common Reuse Principle)1パッケージ内のクラス群は同時に再利用される。 [7] REP(Release-Reuse Equivalency Principle)再利用の粒度はリリースの粒度と一致する。 [8] CCP(Common Closure Principle)1パッケージ内のクラス群は同種の変更に対して閉じている。 [9] SRP(Single Responsibility Principle)1つのクラスに1つの責務(責務=変更理由)。 [10]) Robert C. Martin、"Agile Software Development"、Prentice Hall、2003 |
||||
はじめにもどる |