オブジェクト指向開発の原則とテスト駆動

アンクル・ボブによるオブジェクト指向開発の原則を見直すことからはじめた、オブジェクト指向の重要な意義である依存性の管理とテスト駆動開発に関する私見の整理。

オブジェクト指向開発の原則

まずはアンクル・ボブの記事から:


出典:ArticleS.UncleBob.PrinciplesOfOod

ここで取り上げられているオブジェクト指向の原則が果たす役割について、アンクル・ボブは次のように説明します。

These principles expose the dependency management aspects of OOD as opposed to the conceptualization and modeling aspects. This is not to say that OO is a poor tool for conceptualization of the problem space, or that it is not a good venue for creating models. Certainly many people get value out of these aspects of OO. The principles, however, focus very tightly on dependency management.


これらの原則が示すのは、オブジェクト指向開発における依存関係の管理であって、概念化やモデリングという側面ではありません。だからといって、オブジェクト指向が問題領域に対する概念化を行うにはツールとして貧弱である、だとか、モデルを作るには向いていないとか、そういうことを主張する訳ではありません。多くの人々がオブジェクト指向のこういった側面から多くを得ていることは確かなのです。しかし、この原則はあくまでも依存性の管理に焦点を当てるということです。

ここで紹介されるオブジェクト指向の原則は以下の通りです。

クラス設計に関する5つの原則
  英語名称 日本語名称 説明
SRP Single Responsibility Principle 単一責任原則 あるクラスを変更する理由は一つ以上あってはならない。
OCP Open Closed Principle オープン・クローズド原則 クラスに変更を加えることなく、振る舞いを変えることができなければならない。
LSP The Liskov Substitution Principle リスコフ置換原則 子クラスは親クラスと置換可能でなければならない。
DIP The Dependency Inversion Principle 依存性逆転原則 具象ではなく、抽象に依存せよ。
ISP The Interface Segregation Principle インターフェイス分離原則 クライアントに特化した適切な粒度のインターフェイスを作成せよ。
パッケージ内の凝集性("cohesion")に関する3つの原則
  英語名称 日本語名称 説明
REP The Release Reuse Equivalency Principle リリース・再利用等価原則 再利用の粒度は、リリースの粒度である。
CCP The Common Closure Principle 閉鎖性共通化原則 同時に変更されるパッケージは同一パッケージに格納される。
CRP The Common Reuse Principle 再利用性共通化原則 同時に利用されるクラスは同一パッケージに格納される。
パッケージ間の関連に関する3つの原則
  英語名称 日本語名称 説明
ADP The Acyclic Dependencies Principle 依存性非循環原則 パッケージ間の依存関係が循環してはならない。
SDP The Stable Dependencies Principle 安定性依存原則 安定する方向に依存せよ。
SAP The Stable Abstractions Principle 安定抽象化法則 安定するほど抽象度は増加する。



いずれもレイヤ構造が整った設計を行う上では非常に重要な原則となっています。詳細についてご興味がある方には以下の書籍が参考になると思います。

アジャイルソフトウェア開発の奥義

アジャイルソフトウェア開発の奥義

※上記の原則だけにとどまらず、非常に深い知識からソフトウェア開発に関するノウハウを説明した良書です。

レイヤ化

アンクル・ボブが言っている「モデリングパラダイムとしてのオブジェクト指向」と「依存関係を管理する技術としてのオブジェクト指向」という対置は、言われてみれば極めて明快ですが、あまり明確に意識されることがないのではないでしょうか。このような概念の切り分けは碩学ならではの技ですが、こういった体系をインストールすることで見えてくるものも色々あるのではないかと思います。さて、「モデリングパラダイム」も「依存関係の管理」も共に重要な概念ですが、構造としてはモデリングパラダイムとしてのオブジェクト指向も依存関係の管理技法によって下支えされる必要があります。


依存関係の管理が大切である理由の一つは、この概念がソフトウェアのデザインにとって本質的な「レイヤ化」に関わるものであるからということができます。例えば、

  • 2クラス間の直接的な依存関係を避けるためにインターフェイスを切り出すこと。
  • さらにインスタンス生成時に発生する具象クラスへの依存を避けるためにファクトリーを導入すること。
  • ファクトリー自体も抽象化して、具象クラス間の依存関係を完全に断ち切ること。

これらはいずれもオブジェクト指向プログラミングにおける重要な基礎ではありますが、単純に2クラス間の依存関係を断ち切るためだけにしてはあまりにおおげさです。このような抽象化を考える上で重要なのは、実はこのような依存関係の切断によって、モデルに対して新しいレイヤが導入されているということを明確に自覚することなのではないでしょうか。


もちろん「クラス分割=新しいレイヤの導入」かと言われれば決してそのようなことはなく、例えば、SRPに準拠した結果として生まれる複数のクラスが同一のレイヤに属することは極めて多くあります。ここで自覚するべきなのは、新しく抽出されたクラスが受け持つ責務の独立性が決して絶対的なものではなく、特定の抽象度からモデルをとらえた時にあらわれる概念にすぎないということです。表現を変えれば、自分自身がどのレイヤから見ているのかを忘れてはいけないということですね。


このような観点からすると、オブジェクト指向モデリングとは「明確にレイヤ化された諸概念によって美しく構築されたモデルを生み出す技術」と整理することができるかもしれません。

依存関係の管理とテスト駆動

依存関係を適切に管理することによってもたらされる大きなメリットのもう一つが「テストの容易さ」であると言えます。この"Testability"に関しては、記事の中では明確に触れられませんが、上記著作の中では丁寧に説明されています。この「依存関係が美しく整えられた階層構造を生み出すこと」と「テスト駆動を実践すること」は見た目以上に密接に絡み合っています。その理由は「テストされていない独立性」というものは結局のところ観念的なものでしかないために、特定のクラスないしパッケージが「実際には存在しているんだけれども実装者が見落としている下位のレイヤ」にどっぷりと依存していることがありえるから、ですね。


単体テストコードを後から書くタイプの開発手法では、ロジックの不備が後に明らかになるのと全く同じように、レイヤ構造の不備に気がつくのも後からということになってしまいます。しかし、ロジックの修正がほとんどの場合そのクラス内に限定できるのと対照的に、レイヤ構造の見直しはデザインの大幅な変更を伴うことが珍しくありません。テスト対象の修正が不可能である場合には、(ロジック自体が間違っている訳ではない場合にはなおさら)テストコードの方で対応する他なく、結果として「何だかよく分からないテストコード」が生み出されることになってしまうのではないでしょうか。この「何だかよく分からないテストコード」は「なんだかよく分からないコード」以上に性質が悪く、後の拡張・変更にとって大きな障害になってしまいます。


***


モデリングパラダイムとしてのオブジェクト指向が生み出すべきものが、「明確にレイヤ化された諸概念によって美しく構築されたモデル」と「それを適切に表現した実装」であるとするならば、そのような実装に対するテストコードもモデルを的確に表現したものでなければならないはずです。このような「モデル−実装−テスト」が一体となった開発こそが、真に「アジャイル」と呼ばれるにふさわしいのだろうと思いつつ、その難しさに苦悶しています。



関連リンク

  • Spolsky vs Uncle Bob
    • ジョエルとアンクル・ボブのTDDに関する議論を整理した記事