クラス設計の原則

みんなのウェディングの高井です。

クラスベースのオブジェクト指向プログラミング言語を利用している人であれば、クラスとは、ありふれていて普段から利用するものです。にもかかわらず、良いクラスをつくるというのは、なかなかに難しいことです。

先日、みんなのウェディングでアルバイトをしてくれている学生さんのコードレビューをしていたときにも、それを強く感じました。

実践的プラグマティックには「ソフトウェアの規模や文脈にあわせて、適切に抽象化していただきたい」という以上のことを言っても仕方がないところなのですが、それだけでは経験の浅いプログラマーにとって、まったく分からないという話になってしまいます。

というわけで、今回はクラス設計の原則についてのお話しです。

Bertrand Meyerのクラス設計の原則

Bertrand Meyerは『オブジェクト指向入門 第2版』の中で、クラス設計について章をひとつ割いています。その中で触れているのが、ふたつのクラス設計の原則です。

コマンドとクエリの分離原則
ファンクションは抽象的(abstract)な副作用をもたらしてはならない。
オペランドの原則
ルーチンの引数にはオペランドのみを入れるべきである(オプションは入れない)。

最初の「コマンドとクエリの分離原則」は、オブジェクトを修正するためのコマンドと、オブジェクトについての情報を返すためのクエリをわけたうえで、クエリとして結果を返すメソッドについては、副作用をもたらしてはならないという原則です。

ようするに、オブジェクトの状態を問い合わせる操作で、オブジェクトの状態が変わるのはダメですよ、という話です。

もうひとつの「オペランドの原則」は、メソッドの引数についての原則です。メソッドの引数としては、操作対象であるオブジェクト(オペランド引数)だけを渡し、操作のモードをあらわすオプションについては含めるべきではないという原則です。

これは、クラスの再利用性に関わる原則です。メソッドの操作対象は、そう変化することはありません。それに対してオプションは、クラスが進化する過程で増減します。増えたり減ったりするオプションのために、メソッドのインタフェースを修正するというのは、良い方法ではありません。

Mayerが重視するのは、クラスをいかに抽象化に利用するかという点です。クラスは、ソフトウェアを抽象化するための手法です。クラスを利用することで、プログラムからデータの物理的表現やその操作から抽象することができ、分かりやすいインタフェースを備えたモジュールの集まりとしてソフトウェアを構築することができるようになります。

Robert C. MartinのOODの原則

Robert C. Martinは、「The Principles of OOD」という記事で、SOLIDと呼ばれているオブジェクト指向設計の原則を紹介しています。これは、五つの原則の頭文字をとったものです。

SRP(The Single Responsibility Principle)
クラスの責務は単一でなければならない。
OCP(The Open-Closed Principle)
オブジェクトは拡張に対して開いており、修正に対して閉じていなければならない。
LSP(The Liskov Substitution Principle)
派生クラスは、その基底クラスと置換可能でなくてはならない。
ISP(The Interface Segregation Principle)
クライアントに対して利用しないインタフェースへの依存を強制してはならない。
DIP(The Dependency Inversion Principle)
具象化されたものではなく、抽象化されたものに依存するべきである。

全体の解説については、こちらの記事 に譲るとして、最初の「単一責務の原則(SRP)」について説明をしておきます。というのも、彼の著書である『Clean Code アジャイルソフトウェア達人の技 』の中では、クラスについて「単一責務の原則」を中心的に取り上げているからです。

「単一責務の原則」は、「クラスは、ただ1つの責務、つまり変更の原因となるものを持たなければなりません」と説明されています。「変更の原因となるものが1つだけ」ということが、「責務」とどう繋がるのか分かりにくいかもしれませんが、具体的に考えると理解しやすいでしょう。

たとえば、あるクラスの変更の理由を考えたときに、読み込むファイルのパスが変更になったときに修正されることもあれば、読み込むファイルの形式が変更になったときに修正されることもあったとします。このときに、そのクラスには「あるパスからファイルを読み込む」という責務と「ファイルフォーマットを解釈する」という二つの責務が存在すると考えられるのです。

Robert C. Martinが重視するのは、クラスの責務です。クラスの責務を小さくすることで、理解しやすく、保守しやすいシステムを構築することができます。

Kent Beckにとってのクラス

Kent Beckがクラス設計について言及するとき、それがコミュニケーションの手段であるということを強調します。『実装パターン』では、次のように述べています。

全体のコストを減少させるための戦略として、私があらゆるプログラマに要請しているのは、プログラマ同士のコミュニケーションに重点をおくことによって、保守フェーズでのコード理解にかかるコストを減らす取り組みだ。

プログラマたちが実践してきた「読みやすく、理解と修正が容易なコードを生み出すための習慣」を体系化することでパターンとして記述し、それを身に付けることが良いクラスを生み出すための第一歩というわけです。

そうなると、「クラスは、多くの具体的な事柄を記述できるので、コミュニケーションにおいて重要なものだ」とも書いている通り、クラスは、自分の意図を伝えるためのコミュニケーションの道具立ての一つということになります。パターンに寄り添うことで、より良いコミュニケーションを達成できるのです。

まとめ

クラス設計については、多くの識者が多くの言及をしています。共通して言及されているものもあれば、そうでないものもあります。良いクラスについて、実にさまざまな意見があり、統一されたものはありません。

いずれにせよ、強調しておきたいのは、良いクラスというものが、分析的に定義できるというものではなく、経験的に理解されるものだというこということです。より多くのコードを書き、より多くのコードを読むことで、より良いクラスをつくることができるようになります。

ですから、教条的にならずに実践的であることも大切です。まずは手を動かしていくことから始めていきましょう。