blog

紫陽花

オブジェクト指向設計 実践ガイド 読んだ

オブジェクト指向設計 実践ガイド 」を一通り読み終えました。

結構読むのに時間がかかってしまったのですがとても良い内容でした! オブジェクト指向についてはなんとなく知っているけれども、しっかり実践で活用しきれていない中級プログラマーの方が読むといいかもしれません。

よくオブジェクト指向関連の本に出てくるワード、「ポリモーフィズム」「ダックタイピング」「単一責任原則」「デザインパターン」など。

この本では実際にそれらのワードについてソースコードリファクタリングのような形(before|after)で分かりやすく説明してくれています。

なので、読んでいると「あぁ、これってこういう風に使うと便利なのか!」という気づきが多くありました。

実際の現場でオブジェクト指向で組まれたプログラムを見たことがあるのですが、実践ガイドを読む前と、読んだ後ではコードの見方が大きく変わりました。

他の方も書評で書かれていたのですが、文章が少し抽象的な表現が多かったかなと。 読んでてこれってどういうこと?みたいなところが所々ありました。 読み手の理解力の無さが原因でもあるのですが(笑)、自分は読み解くために2、3回時間を空けて読み直すことで理解を深めていきました。

今までオブジェクト指向関連は、ネットの記事や先輩の説明などでなんとくなく理解していたつもりでいました。 今回本を用いて体系的に学ぶことで型を学ぶのは大事だなと改めて思いました。

特に印象に残ったところを簡単にメモ書きしておきます。

メモ

  • 実用的な設計とは、未来を予測するものではなく、動くための余地を設計者に残すものです。
  • 彫刻家がノミとやすりを持つように、オブジェクト指向の設計者も道具を持つ。それが、原則とパターン
  • 設計者の目標は、機能あたりのコストが最も低い方法でソフトウェアを書くこと
  • 実践とは配られたカードでベストを尽くすことの積み重ねなのです
  • 設計とはアプリケーションの可変性を保つために技巧を凝らすことであり、完璧を目指す行為ではありません
  • 見通しが良い(Transparent)・合理的(Reaspnable)・利用性が高い(Usable)・模範的(Examplary) TRUEなコードを書く
  • メソッドも単一責任にし、なるべくコードを単純化する
  • オブジェクト指向アプリケーションは「クラスから成り立つ」が、メッセージによって「定義される」
  • パブリックインターフェースとプライベートインターフェースの使い分けで不要な関連づけを防ぐ
  • 依存オブジェクトの注入・ダックタイピング・テンプレートメソッドパターン・ファクトリーパターン・コンポジション・共通モジュール

12/16(金) 追記

Qiitaでjoker1007さんが「オブジェクト思考設計実践ガイド」を読め。と言及していました。

俺が悪かった。素直に間違いを認めるから、もうサービスクラスとか作るのは止めてくれ

Railsに用いるserviceクラスの問題点について述べられています。 後半にはオブジェクト指向設計に関する重要な要点を完結にまとめてくださっています。感謝 学習のメモとして引用させてもらいます。

  • オープン・クローズドの原則
  • 単一責任の原則
  • デメテルの法則
  • クラス同士の依存関係の管理
  • 実装ではなくインターフェースに依存する
  • ダックタイプとクラスの中に内在するインターフェース
  • 集約クラス

オープンクローズドの原則

先に述べた様に、変更が発生した場合に自分以外の外に影響がもれずに閉じている状態が望ましい。 でないと、ある処理を変えた時に連鎖的に調査範囲が拡大して変更をコントロールできず死ぬ。 もしサービスクラスが、モデルレイヤーのメソッドをいくつも直接呼び出しているとすると、モデルにちょっとした変更が入ったらぶっ壊れるかもしれない。 そんなのがいくつもあると辛みしかない。

単一責任の原則

一つのクラスが担う責務は一つにすること。 ActiveRecordを元にしたクラスはRDBという根幹になるデータ構造と紐付いているため、複数の役割を担うことになる可能性が高い。しかし、それを別のクラスに分割することはできる。 責任範囲でクラスを分けることを意識しないと半端に元のクラスと結合した扱いづらいクラスが生まれることになる。 モジュールで分けても本質的には抱え込んでいる責務を分けたことにはならない。 どういった責任をどういった名前を持ったクラスに与えるべきなのかを考えて、クラスを作っていくことが大事だ。

デメテルの法則

あるクラスが触っていいのは、自分が直接知ってるクラスだけという状態を維持しなければならない。 これを守ろうとしないと、あるクラスがあちこちのクラスに依存し、処理の詳細まで抱え込んだ結果、変更が発生した時の影響範囲が拡大し過ぎて爆死することになる。 また、デメテルの法則に反したコードは、隣人クラスに問い合わせして取得した結果を元に自分で判断する様なコードになることが多い。 こういったコードは前述した様にテスタビリティが悪い。

クラス間の依存関係

自分んおクラスが直接持っている情報以外を触るということは、外部の何かの知識に依存するということであり、それが存在していないと処理が完結しないことを意味する。 自分以外のクラス名や、それが持つメソッド名、メソッド名、メソッドの引数、メソッドの返り値、外部クラスの処理結果を知れば知る程依存関係が強くなり、変更が辛くなる。 依存が少なくなる様な構造を考える必要がある。 また、依存関係は一方向に保つこと。相互依存するような関係はオブジェクトの生成プロセスや処理の流れを激しく複雑にする

メソッド名の抽象化、引数の取り扱い、依存オブジェクトの抽出 etc... 少し意識を変えて書くだけで依存関係を適切な形に変更できる。

インターフェースに対して依存する

あるクラスが知っておくべき知識をできるだけ抽象化しておいた方が良い。 具体的な処理内容はとても変わりやすく、その内容に依存しているとすぐ壊れる。 そして単純に読みにくさが増す(実装を行ったり来たりしないと何が起きてるか把握できない)。 ポリモーフィスムの力を活かす上でもインターフェースを意識したコードを書くことは重要。

例: 抽象化 carやbikeではなくvehicleとする

ダックタイプ

Rubyという言語は何も記述しなくても、メソッドの定義だけを元に対象が何者であるかを判断する。 しかし、それはインターフェースが消えて無くなったわけではない プログラマが逐一書かなくてもいいようになっているだけで、その処理に利用される各クラスはインターフェースの実装としての姿を持っている必要がある。 どの様にインターフェースを見出してクラス間の依存関係をどう表現するかは、プログラマの脳内にある業務領域とクラス構造のマッピングにかかっている。 プログラマが楽できる分、その知識に信頼性が求められている。

集約クラス

クラスが持つ責任範囲には境界線がありグルーピングできることが多い。 ここから先の詳細は全部こいつに任せる、後は知らなくていい、そういうクラス同士の関わり方の地図を作っておかなければいけない。 そうでないと、複雑さが組み合わせによって無限に増えて行く。 Ruby/Railsでは、それを言語レベルで強制する手段は無いので、プログラマの責任として守らなければいけない。 V字型により詳細を知っているクラスにブレイクダウンしていく動きを想像して欲しい。 モデル層の中にも階層構造は存在するし、処理の大枠だけを管理するコントローラーの様な存在はある。