もっと早くオブジェクト指向設計実践ガイド読んどけばよかった
@joker1007さんが激推ししてたのでオブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方を読んだ。
なんかすんませんw Sandi Metz本はここ数年でトップクラスに良い本だったのでオススメです。
— ジョーカー (onkさんに返済完了) (@joker1007) 2017年12月9日
#railsdm
内容としては、オブジェクト指向設計の核となるものを初めての人でもわかりやすく理解できるように書かれた本という感じ。
この手の本は静的型付け言語でかかれたものが多いがRubyで書かれてるのでゆるふわなwebエンジニアにも読みやすそう。
流行りのDDDをやるにもまずオブジェクト指向がしっかり理解できてないと厳しいし、まずはしっかり土台を固めようぜみたいな。
オブジェクト指向を理解した気になっている人とか、新卒で入社してきたwebエンジニア諸氏には3回くらい読んでほしい程度には網羅的でちょうどいい難易度だった。
経験談として、新卒でこういう本を読んでいくつかwebアプリ書いてPRで全修正とかされながら学んでいくみたいな経験をしっかりできた人とそうでない人では数年後のスキルに大きな差がでてくると感じている。
ある程度職歴を重ねると 変更に耐えうるコードを書くための設計力 というものがどれほど尊く高度なスキルであるか思い知らされるものであるが、この本はそうしたスキルを得るための入り口になりえるだろう。
直近の話だが、複数のリソースを一つの通知フィードに流す機能を実装する際にロールをmodule化して各リソースに共通のインターフェースを持たせるみたいな実装があってまさにこの本ででてくるdack typingのパターンだった。
これを知らなかったら分岐地獄で死んでたはず。
実践でも頻出する設計課題にどう立ち向かうか、そのために必要な最低限の武器は若いうちに装備しておくべきであると改めて感じさせられた。
最後に、株式会社GIFMAGAZINEではオブジェクト指向設計実践ガイドをよく理解したエンジニアを募集しています。
- 興味ある人はこっちに連絡くれてもOKです → @razokulover
以下は読んでる時にScrapboxで取った読書メモです。汚いので飛ばしてok。
- 単一責任原則
- クラスの説明を書いてみて、「OOをするクラス」ではなく「OOとXXをするクラス」のようにandがでてきたら責務を持ちすぎているクラスの可能性がある
- データに直接アクセスするのではなく、メソッド呼び出しの形に変えると変更に強くなる
- もし@priceを消費税込のpriceメソッドみたいな感じで利用するようにしておくと、priceを各所で参照してるときとかあとで税率変わった時に変更箇所が1点に集中されるので便利
- 他のクラスで利用するインスタンスを生成するクラス -> ファクトリー
- 依存の排除
- 抽象すればするほど、そのクラスは変更が少なくなる
- 依存されてる数と要件が変わる可能性は直交する
- 依存されてる数が多くて要件が変わらない -> 抽象クラス
- 依存されてる数が少なくて要件が変わりやすい -> 具象クラス
- 単一責任原則を守ってクラス・メソッドが実装できていれば自然と↑のようになる
- 依存オブジェクトの注入
- オブジェクト同士の依存を排除するため
- 外部から(例では引数から)オブジェクトを注入して、振る舞いの中ではその注入したオブジェクトにメッセージを送るだけ。メッセージを送るだけなので注入するオブジェクトを変更すれば容易にオブジェクトの差し替えも可能。オブジェクト同士の依存関係がなくなる!メッセージのみに集中すればよい
- インターフェースの設計
- 何を開き何を閉じるか
- UMLでの説明で具体的なコードがでてこないのでクソ眠くなった
- デメテルの法則
- メソッドチェーンを連鎖させすぎるなみたいな感じ
- hash.keys.map.join(',')みたいなreturnするオブジェクトが連鎖するメソッドを確実に実装してるなら問題なし
- 複雑に連鎖したメソッドチェーンが発生するのはオブジェクトが正しいパブリックインターフェイスを実装していないから
- つまり例えば、customer.bycicle.departみたいなメソッドチェーンはcustomer.rideというインターフェースが本来必要なのだ!っていうこと
- ダックタイピング
- 動的型付け言語でのみ可能な特有の(便利な)手法
重要 なのは、 オブジェクト が 何で「 ある」 かでは なく、 何 を「 する」 かなの です。
- 通知機能の実装で複数のリソースを一つのタイムラインにまとめて表示したいときにダックタイピングで実装してうまくいった経験がある
設計 の 目的 は コスト を 下げる こと です。 この 物差し を い つ 何時 でも 携え て おき ましょ う。 ダックタイプ を つくる こと で 不安定 な 依存 が 減る なら ば、 そう すれ ば よい の です。 最良 の 判断 を し ましょ う。
- 継承
- 継承とはオブジェクトに送られたメッセージに応答すべきメソッドが無かった時、自動で別のオブジェクトにそのメッセージを委譲する仕組み
- スーパークラスで実装必須の振る舞いにはNotImplementedErrorを実装しておくべき
- initializeも親に実装して具象クラスが知っておくべき情報をできるだけ減らす
- これが本当に便利なのか難しそう
- リスコフの置換原則
- スーパークラスとサブクラスは入れ替え可能でなければならない
- テンプレートメソッドパターン
- 抽象クラスで振る舞いのデフォルトの挙動を定義し、具象クラスでそのメソッドに手を加えたりする
- これによってサブクラス特有の処理がはっきりする
- オーバーライドしてるということはデフォルト挙動と異なるということなので
- superはできるだけ使わないようにする
- できるだけ抽象クラスと具象クラスの依存は減らす
- 代わりにフックメソッド
- モジュール化する
- 役割(ロール)で振る舞いを抽象
- なぜクラスの継承ではダメ?
- 抽象クラスを継承したらサブクラスでは必ずコードを利用するように設計しないといけない。モジュールも同様。
- オブジェクトを組み合わせる == コンポジション
- 複数のオブジェクトをhas-aの関係で組み合わせる
- has-aのaは部品。オブジェクトを構成する部品。一部。このオブジェクトの生成にはFactoryクラスをつくって引数によって種類のデータの違う部品を生成するなどすると汎用性高くて良い。
- Enumerableをincludeしてeachとかsizeとか使えるようにするのよく見る
- Forwardableをextendしてdef _delegator @object, :each, :sizeみたいに書くと@objectにeachとsizeの振る舞いが設定される。使ったことなかった。
一般的 な ルール として は、 直面 し た 問題 が コンポジション によって 解決 できる もの で あれ ば、 まずは コンポジション で 解決 する こと を 優先 する べき です。 継承 の ほう がより 良い 解決 法 で ある と はっきり 言い 切れ ない とき は、 コンポジション を 使い ましょ う。 コンポジション が 持つ 依存 は、 継承 が 持つ 依存 よりも はるか に 少ない もの です。 です から、 コンポジション が 一番 の 選択肢 で ある という こと は、 頻繁 に あり ます。
- 継承は正しく階層化されて設計されてる時以外使うべきじゃ無いっぽい
- 将来求めるクラスの振る舞いが変わるかもしれん。そのときその継承は破綻する。
- 将来の変更に弱い
- 将来求めるクラスの振る舞いが変わるかもしれん。そのときその継承は破綻する。
- 複数のオブジェクトをhas-aの関係で組み合わせる
- 結局のところ 継承 と コンポジション と ダックタイピング(モジュールによる) はどう使い分けるべき?
- 変更可能なコードを書けるスキルはめっちゃ高度
「 プ ラ イ ベ ー ト メ ソ ッ ド は 決 し て 書 か な い こ と 。 書 く と す れ ば 、 絶 対 に そ れ ら の テ ス ト を し な い こ と 。 た だ し 、 当 然 の こ と な が ら 、 そ う す る こ と に 意 味 が あ る 場 合 を 除 く 」
- プライベートメソッドを書く場合は複雑性を時間的なコストやリファクタリングのコストの問題からしょうがなく別の場所に追いやる必要がある時のみ書くべし。