「ちょうぜつソフトウェア設計入門」第5章 オブジェクト指向原則 SOLID まとめと感想

ソフトウェア設計

本書について

ちょうぜつソフトウェア設計入門 PHPで理解するオブジェクト指向の活用

技術書「ちょうぜつソフトウェア設計入門 PHPで理解するオブジェクト指向の活用」レビューとまとめ。Bobおじさん本を読む前の準備として最適な一冊。からリンク。

第5章 オブジェクト指向原則 SOLID

5-1 SOLID
5-2 単一責任原則
5-3 解放閉鎖原則
5-4 リスコフの置換原則
5-5 インターフェース分離原則
5-6 依存性逆転原則
column: オブジェクト指向らしさ

アーキテクチャを俯瞰した視点での設計原則が、第2章パッケージ原則だった。より小さなクラス設計では、このSOLID原則が適用できる。

SOLID

SOLIDとは、

  • Single Responsibility Principle (SRP)
  • Open-Closed Principle (OCP)
  • Liskov Substitution Principle (LSP)
  • Interface Segregation Principle (ISP)
  • Dependency Inversion Principle (DIP)

の頭文字を取ったもので、「アジャイルソフトウェア開発の奥義」でRobert C. Martinが提唱したもの。アジャイルソフトウェア開発の奥義では、SOLDIの順序になっており、SOLIDという略称は使われていない。その後の「Clean Architecture」でSOLIDとして紹介された。

(注意)本書では、ISPがInterface Separation Principleになっていたが、恐らくInterface Segregation Principleのミスと思われる。

SOLIDは、オブジェクト指向のクラスをどう設計するかを例にしているが、一般的なソフトウェア開発全般に応用できる考え方。パッケージ原則と同じ目的観で、パッケージ原則は全体、SOLIDはその中身という感じ。

もし今までSOLID原則を学習したことがなかった人がこの原則を知ると、今までの書き方は何だったんだと感じられる、そんな原則と思う。いかにメンテナンス性よくコードを組み立てるか、オブジェクト指向を使えば依存方向を自由に変更できるらしいけど、どういうこと?といった疑問の答えを、この章で学べる。
第1章クリーンアーキテクチャのところでは、一番外側の層 (DBやUI等)が、内側の層(メインルーチン等)に依存するという説明があった。この一見、そんな向きに依存できるの?という疑問を投げかけていた部分を、この章で解決することができる。

この一見不自然な依存関係をどのように実現するかに、オブジェクト指向が役に立ちます。クリーンアーキテクチャに欠かせない特有の要素、本当に訴えたいことの核心は、プログラミングテクニックを使った、この依存方向の自由な制御です。

読み物的に、この章に繋がっている。

単一責任原則 (Single Responsibility Principle (SRP))

いわゆる、「ひとつのクラスにひとつの責務」という指針。シンプルだが難しい原則。ここでの「責務」とは何なのか?
クラスの責務というのは、機械的に決まるものではなく、将来の保守開発への想像から設計するもの。どういった時に、そのクラスに変更が起きるか?複数の理由で変更されることは無いか?この本では、例としてニュース記事の入稿と購読が挙げられている。これらをまとめて Article Operationクラスにしてしまうと、ニュース記事を書く側の事情が変わっただけなのに、購読ユーザーに影響を与えてしまったり、購読サービスを拡充したいだけなのに、ニュース記事を書く側に影響を与えてしまうことになる。こうした変更理由を考えて、クラスを分ける必要性が説明されている。この場合は、「入稿」と「購読」でクラスを分ける方が良い。

最後に書かれていることが、重要だがつい、まあいいか、と無視してしまう考え方と思う。こうしたことを着実に面倒くさがらずにやることが、今後の生産性に大きく繋がっていくのだと思う。

責務を事前に予測できるとよいのですが、現実には、コードを書いてしまってから「これはひとつの責務だったか」と気づく場合がよくあります。開発を進める中で、異なる複数の要素を同時に変更しなければならない状況が起きたときは、作業をそのまま続ける手を止めて、いつでもクラスの分け方を考え直しましょう。

こうした責務を考えて分割された設計があれば、複数人で作業を分担することも簡単にできるようになるし、初心者がいたとしても影響度の小さい部分の実装だけをお願いすることも可能になると思う。

開放閉鎖原則 (Open-Closed Principle (OCP))

拡張に対してOpenで、変更に対してはClosedであるべきという原則。機能追加はできるけど、うまく動いているプログラムを書き換えて壊してしまうのは避けたい、という当たり前だが難しいことを実現するための原則。ソフトウェア開発において、必ず発生することは仕様変更。特にアジャイル開発では、動いているものを見てフィードバックを受けてまた作り直すことが多いと思う。仕様変更の際、あちこちを変更することになるのは避けたい。この場合、安定度・抽象度等価の原則 (SAP)が役にたつ。コードの変更が起きにくい安定度の高いものほど、抽象度が高いという方針を立てる。そして、今後どんな仕様変更が起きそうかということを予測する。その予測のためには、その仕様の本質を見極める必要がある。

本書では、FizzBuzzを例として、最初にif-elseで組んだNG例から始まり、本質の仕様を見極めてクラス化していく。こんな風に作れるのかと感心した。
基本的には、次のようにして本質部分を抽象として抜き出してから、抽象に依存させる構造を作る。

  1. 本質部分をInterfaceとして、具体的なルールなどをその具象としてクラス化する
  2. コア部分がそのInterfaceに依存するようにする
  3. 仕様をInterfaceを継承して実装する

そうすると、具体的なルールが変わっても、コア部分はInterfaceに依存しているだけのため、変更する必要は無い。しかし、実際の動作は変更されたルールとなる。これにより、拡張にはOpenで変更にはClosedなコードにできる。

リスコフの置換原則 (Liskov Substitution Principle (LSP))

派生クラスの振る舞いは、基底クラスの振る舞いを完全にカバーしなければならないという原則。誤った継承に警鐘を鳴らして、正しい継承をする方法を教えてくれる原則。
リスコフ置換原則の違反は、具象クラスを継承でカスタマイズしようとした際によく起こる。具象メソッドをオーバーライドして、異なる条件を追加してしまうと、よくわからないことになってしまう(この本ではもっと詳細に説明されている)。基本的には、抽象クラスやインタフェースを基底クラスとするべき

リスコフの置換原則に違反すると、下位互換性を保つことができなくなる。バージョンアップしたらこれまでの使い方ができなくなったということがあり得る。
開発中も互換性が保たれているように作ることができれば、しょっちゅうあちこちを手直しするということもなくなり、開発がスムーズになる。

インターフェース分離原則 (Interface Segregation Principle (ISP))

インターフェース分離原則は、インターフェース版の単一責任原則。


interface DataInputInterface { public function write(string $key, mixed $data): void; } interface DataOutputInterface { public function read(string $key): mixed; } interface DatabaseDriverInterface extends DataInputInterface, DataOutputInterface {}

のようにデータベースのread/writeを分離しておけば、readしか不要なアプリケーションクラスは、readのインターフェースだけに依存することができる。つまり、不要なwriteは見えなくなる。

これだけだと、なぜこれが原則なのかというのが分かりにくいが、本書ではUSBデバイスとしてUSBマウスやキーボード、そしてノートPCのキーボード、マウスを扱うプログラムを例として、うまく説明している。悪い設計だけど簡単に思いつくものから、徐々にこうすればこうよくなるけど、まだこういうNGな点があるよね、といった説明で、最終的にすっきりとインターフェースを分離させて変更に強いコードが作り出される。非常に分かりやすい説明だった。

重要なのは、あるクラスがあちらとこちらで連携するときは、そのクラスが複数の関心を持っていることを理解し、実装より先に使用を想像し、複数の使うときの関心を結合させてしまうのを避けること。これはつまり、TDDにもつながることと思う(実際、次の章はTDD)。

依存性逆転原則 (Dependency Inversion Principle (DIP))

この原則こそが、第1章の依存性の方向についての疑問の答えであり、構造化プログラミングに対してオブジェクト指向らしさを決定的に示すもの。
構造化プログラミングの機能分解アプローチでは、アプリケーション全体を部分的な小さな機能部品に分け、その小さな機能部品をさらに小さな詳細機能に分けて作っていく。しかし、これだと上位構造の正しさが下位の実体の動作に依存してしまう問題がある。

オブジェクト指向のアプローチでは、まず本質部分を抽象としてインターフェースで抜き出し、詳細は後回しにして、まずは抽象概念を実装する。その後、詳細の仕様を実装する。そうすると、安定度の高い方向に依存方向を向けることができる。

オブジェクト指向の原則を意識すると、おのずと依存関係逆転原則が起きます。クリーンアーキテクチャを実現するには、この特徴を応用すればよいのです。極端に言えば、クリーンアーキテクチャとは、依存関係逆転原則をアーキテクチャに適用するという、応用方法の一例にすぎない、とも言えます。

とあるように、自然にやれば原則が守られていた、という状態になれることが理想。

column: オブジェクト指向らしさ

明確な定義があるわけではないため、オブジェクト指向の定義について議論するのは意味がない。しかし、ソフトウェア設計の一般論として意義がある形でSOLIDには明確な定義がある。良い考え方は、SOLIDに即した特徴を持っている。といったことが個人の考えとして記されている。自分にとってのオブジェクト指向はこうだというような議論は生産的では無いし、この本の記載内容や著者の方のTwitterの内容も含めて、「オブジェクト指向」に対する著者の考え方は、納得感のある説明だなと思った。

SOLID原則を学べる本や動画講義

最後に、SOLIDに関して学べる本をまとめておく。

この辺りかと思う。個人的には、Clean Architectureから入って、最初はそんなに理解できなかったので、他書で補ってからClean Architectureに入るのが良いと思う。
実は、SOLIDをちゃんと理解したくて、この本を読む前にも、いくつかUdemyの講義も購入して受けていたので下記に示しておく。本では、具体的にコードをどのように書いていくかがわからないこともあるが、動画だと少しずつコードが実際に書かれていくのが見えるので、本+動画で学習するのが個人的には好み。Udemyも含めても、この本の説明は分かりやすかったと思う。もちろん、Udemyはまた別の良さがあるが。

コメント

タイトルとURLをコピーしました