良いコードを書くために必須なオブジェクト指向について学べる本を紹介します

Heads First オブジェクト指向分析を読みました

ブジェクト指向に対する理解を深めたいと思って探していたら、図やイラストが多くて読みやすくて内容もいいといろんなブログやレビューでおすすめされていたので買ってみました。

Heads First オブジェクト指向分析を読んだので勉強になった点や読書した感想を書きます。

僕の感想では、「買ってよかった」です。顧客が解決したい問題を聞き出して要件をまとめ、コードに落とし込み、ソフトウェアを運用していくための知見を本から学び実務にすぐに活かせたので買ってよかったと思います。

本で一番伝わってきたことは、「ソフトウェアに求められる機能は日々変わっていく」ということです。お客さんに使われる限り、ソフトウェアに完成というものは無く、新たな機能が求められます。その要求に応え続けるためにどのよう設計していけばいいかを学ぶことができます。

本書は図やイラスト、写真がふんだんに使われていて読んでいて飽きないように作られていることに感動しました。大事なことは何度も出てきて絶対に見落とさないように強調されています。また、なぜ大事なのかというのもQ&A方式で書かれていて読みやすく頭にすっとはいってきました。

下記に本から学んだことをまとめました。


オブジェクト指向を理解するのに必要な単語

柔軟性

顧客が満足するために必要です。ソフトウェアが解決するのは顧客の課題です。 そして、顧客の課題は毎日変わっていくものなので柔軟性が無いと期待に応えることができません。

カプセル化

再利用を確実に行い既に誰かが解いている問題を再び解かないようにします。 また再利用をしないといことは、重複するコードが散らばっており柔軟性が阻害されている状態です。

低結合

コードの変化する部分を分離します。コードの変換に影響を受ける箇所を減らします。

デザインパターン

毎回作り直さなくともソフトウェアの変更と成長が可能になるようにします。アプリケーションが脆弱になるのを防ぎます。

またデザインパターンを説明の際に使うと他の開発者はあなたの考えている設計を迅速かつ正確に理解できます。 さらにパターンレベルで話をすると議論を設計レベルに留めることができ、オブジェクトとクラスの実装の細々とした詳細を話す必要がなくなります。

委譲

ある作業の責任を別のクラ スまたはメソッドに手渡すことです。 他のクラスの機能を使用しなければならないが、変更をする必要がない時、継承ではなく委譲を検討するべきです。 また振る舞いを変更する必要がなく、自分で実装する責任がオブジェクトに存在しないのであれば、他のクラスに振る舞いを委譲します。

なぜ重要なのか

委譲によってコードの再利用性が容易になります。オブジェクトの振る舞いを処理するコードがアプリケーション全体に広まることがなくなり、オブジェクトは自分の機能だけを処理すれば良くなります。 オブジェクトはソフトウェア内の他のオブジェクトで発生する実装上の変更から遮断されます。

なぜ委譲することで再利用可能なコードになるのか

委譲することでオブジェクト間の独立性が増して疎結合となります。疎結合のオブジェクトは、他のオブジェクトと強く結合していないので、別のアプリケーションにおいても再利用することができます。

ちなみに疎結合とは、オブジェクトが自分の仕事にだけ専念している状態です。単一責任の法則。

オブジェクト指向の原則

  • 変動するものはカプセル化する(変更が容易になる)
  • 実装に対してではなくインターフェイスに対してコードを作成する(拡張が容易になる)
  • アプリケーション内のクラスは、変更される理由を1つだけにする

要件とは正確に何なのか

システムが期待通り動作するために実行しなければならない特定の事柄です。 顧客目線であることが重要です。なので要件は常に変換します。

要件のヒアリング

システムに期待することを(What)に注意して、システムでどのように実現するか(How)は後回しにすることを意識する。

(例)犬用ドアの要件一覧

  • 犬用のドアの開口部は少なくとも30cmの高さが必要。
  • リモコンのボタンを押すとドアが開閉する。
  • 開いているドアが閉められなかったら自動的に閉める。

上記のように顧客がシステムに期待すること(What)を一覧化したら犬用ドアがそれを実現するために何を実行するのか(How)を一覧化する。

システムに期待する動作を顧客目線で考えることがWhatを考えることで、開発者目線でそのWhatをどうやって実現するか(How)を考えることと混ぜてはいけない。

ユースケースとは

特定の顧客目標を達成するためにシステムが実行する内容を記述する。 システム目線であることが「要件」とは違います。ユースケースはシステムの動作方法に関するものです。 内容(what)を扱い、方法(how)については考えないようにします。 ユースケースは、作成者、上司、顧客に対して意味があるように作成する。 分析とユースケースは顧客、マネージャ、その他の開発者に対して現実世界の状況におけるシステムの動作方法を明確にする。

要件は顧客が求める動作が何かを示しているので常に変換します。しかし、それを実現するシステムの動作であるユースケースが優秀であると要件が変更されても迅速に対応できます。

良いユースケースとは

理解しやすい言葉で、システムの処理ない内容を明快かつ正確に説明する。 良いユースケースであれば、テキスト分析が容易に行える。

テキスト分析

名詞(と動詞)に着目して、クラスとメソッドを識別すること。 ほとんどの場合、ユースケースの名詞がクラスとなり、動詞がメソッドとなります。

注意点は、名詞はクラスの候補であって必ずクラスとはならないことです。またシステムの外部にある名詞はクラスにならないことが正しいです。

ユースケースの3要素

1.明確な価値

どのユースケースもシステムに対して明確な価値をもたなければなりません。顧客目標の達成の役に立たないユースケースは無意味です。

2.開始条件と終了条件

どのユースケースも明確な開始地点と終了時点が必要です。何がプロセスを開始させて、何が完了させるのか明確である必要があります。

3.外部起動者

どのユースケースもシステムの外にいる外部起動者(Controller, Adapter)によって開始されなかればなりません。

抽象クラス

抽象クラスは実際の具象クラスが実装されるまでの仮の置き場所である。振る舞いを定義して、具象クラスが実装します。

同じ振る舞いが2箇所以上の場所に存在するのであれば、それを1つのクラスに抽出して再利用する。

インターフェイス

抽象クラスに似ているがインターフェイスはメンバー変数を定義しない。振る舞いのみを記述する。

インターフェイスに対してコードを作成すれば、そのインターフェイスのあらゆるサブクラスをそのコードを扱えるようになる。その中には、まだ作成していないサブクラスも含まれる。

設計原則

定評のあるオブジェクト指向を駆使すればソフトウェアの保守性、柔軟性、拡張性が高まる。

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

クラスは拡張に開放され修正に閉鎖されるべきである。

ソフトウェアは動作するものであれば、できる限り変更されないのが良いので、既に動作することがわかっているコードを拡張することが重要とされます。 OCPの実体はカプセル化と抽象化の組み合わせであります。基底クラスを抽象化して変更されないようにカプセル化してコードを閉じ込めます。 そして振る舞いを追加したくなったときは、抽象化されている基底クラスを継承して拡張します。

修正に閉鎖される….

ある振る舞いを持つクラスがあり、期待通りの処理が行われるようにコードをかけたとしたら、誰にもそのコードを修正できないようにすれば、閉鎖することができる。

….しかし拡張に開放される

ある時、他の誰かが振る舞いを変更しなければならなくなったとしたら、ほとんどすべての状況では問題なく動作するので、完全なコードをいじってほしくは有りません。 しかし、そのクラスを拡張して使いたいと思っています。なので、必要な処理を行うようにメソッドをオーバーライドすることにすれば、クラスは拡張されます。

繰り返し禁止原則(DRY: Don’t repeat yourself)

共通化する事柄を抽出して一箇所にまとまることでコードの重複を防ぐ。

コードがほぼ完成したら、繰返しがないことを確認する。 コードの重複を避け、コード内の振る舞いが1箇所にだけ存在するようにする。

DRY はコードだけでなく、要件にも適用される。実装対象のソフトウェアにおいて、各フィーチャと要件は1箇所にだけ存在すべきである。

DRYの目的はコードの重複を避けることですが、どこから参照すればいいのか迷うことを減らすことにも役立ちます。

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

「クラスを変更する理由は1つ以上存在してはならない」、言い換えると「クラスに変更が起こる理由は1つであるべき」という原則です。 なぜ変更する理由(役割)が単一でなく複数あるといけないのかというと、役割が複数あると、その役割の数だけ変更する理由が増えてしまうためです。 すなわち、複数の役割が異なった理由で変更される可能性があるということです。 この他にも、属性の値の保存方法がタブ区切りのTSV形式のファイルに仕様変更された場合や、そもそも保存場所がファイルではなくデータベースに変更された場合などもクラスを修正する必要が発生します。

単一責任原則に沿ってクラスがひとつのことだけを行うようにすると、コードへOCPを適用するのが容易になる。

注意点

『Clean Architecture』において、アンクル・ボブは、SRPが「どのモジュールもたったひとつのことだけを行うべき」とする原則だと誤解されがちである理由として、「名前があまりよくなかった」*13と述懐している。

SRPの分析

クラスの振る舞いを下の図で埋めていくことで、振る舞いが言語されSRPに即してるかどうかを判断しやすくなる。

___のSRP分析
___は自分自身を___する
___は自分自身を___する
___は自分自身を___する

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

サブタイプは基底タイプと置換可能でなければいけない。 サブクラスは、要求事項を(基底クラスよりも)増やすべきではなく、できることを(基底クラスよりも)減らすべきではない。 LSPに従うことで、自信を持ってポリモーフィズムを使えるようになり、期待しない結果が生じる心配なしに、基底クラスを参照している派生クラスを呼び出せるようになります。

他のクラスの機能を使用しなければいけないが、変更する必要がないときは継承ではなく、委譲を検討する。

また別の説明では、

SがTの派生型であれば、プログラム内でT型のオブジェクトが使われている箇所は全てS型のオブジェクトで置換可能であるべきという原則です。

この時Tはオブジェクトやクラスなどとは限らず、現代であればインターフェースなどで表現されることが多いでしょう。 Sは継承やダックタイピングやインターフェースの実装など、あらゆる手段で、Tを拡張した何かです。 一言でまとめると、型の派生、インターフェースの実装、ダックタイピングなどを行うなら、派生するベースにだけ依存して、それを拡張する側の知識は使うなというのがLSPです。

updatedupdated2020-05-212020-05-21