rhanda | 元銀行員Web系エンジニアの日記

実務未経験からWeb系受託開発企業に転職したひよっこエンジニアが覚えたことや日々の感情を残すブログ

『オブジェクト指向設計実践ガイド』読みました

構成

第1章 オブジェクト指向設計
第2章 単一責任のクラスを設計する
第3章 依存関係を管理する
第4章 柔軟なインターフェースをつくる
第5章 ダックタイピングでコストを削減する
第6章 継承によって振る舞いを獲得する
第7章 モジュールでロールの振る舞いを共有する
第8章 コンポジションでオブジェクトを組み合わせる
第9章 費用対効果の高いテストを設計する

感想

「振る舞いとデータをまとめたオブジェクトを使うこと」が、どのように変更のしやすさに寄与するのかという話をたくさん見ることができたと思いました。
目指すコードは「見通しが良く、合理的で、利用性が高く、模範的」なものであることと、 設計の目的は「今必要な役割を果たしながら、簡単に再利用できて、将来的にも変更しやすいコードを書くこと」であることを都度思い出させながら進む本だったので、何故こうしたいのかを意識しながら読めて良い感じでした。
新たにクラスを追加したい時やリファクタリングしたい時に使える知識が得られたと感じたのと、とりあえずもしクラスを追加する機会ができたら、単一責任のクラスになるように考えてみたいと思いました。

メモ

  • 設計とは
    • 「今必要な役割を果たしながら、簡単に再利用できて、将来的にも変更しやすいコードを書く」ためにやる
    • 繰り返しのフィードバックを頼りに進む。フィードバックは適度な時間ごとに行われるべき。
    • なのでアジャイルソフトウェア運動の反復的な手法は、適切に設計されたオブジェクト指向アプリケーションを作るために良い
    • 設計が早すぎる(つまり必要な調整がまだわからない時点で設計が行われる)と、初期の間違った理解がコードに埋め込まれる
  • アジャイルで正しいとされる「顧客が本当に求めるものを生み出すためのコスパの高い方法」は、顧客と共同作業をすること
    • ソフトウェアは一度に小さな単位で作り、その時にそれぞれの単位が次のアイデアを変える機会となることを目指す
  • BUFD(Big Up Front Design:詳細設計)
    • 提案されたアプリケーションの全ての機能の想定される未来の内部動作を全て特定し、完全に文書化すること
    • アジャイルが正しいならば、これは全く意味がないと言える
  • オブジェクト指向設計の目的は「変更にかかるコストを下げること

単一責任のクラスを作る

  • 変更が簡単なアプリケーションは再利用が簡単なクラスから構成される。2つ以上の責任を持つクラスは再利用しづらい
  • クラスが単一責任かどうかを見極めるには?
    • クラスの持つメソッドを質問に言い換えた時に、意味をなす質問になっているか
      • 「Gearさん、あなたの比を教えてくれませんか」
      • 「Gearさん、あなたのgear_inchesを教えてくれませんか」←しっくりこない
      • 「Gearさん、あなたのタイヤのサイズを教えてくれませんか」←タイヤに聞けよ
    • クラスを1文で説明できるかどうか
      • 「それと」が含まれたりすると複数責任を負っていそう
  • 凝縮度
    • クラス内の全てがそのクラスの中心的な目的に関連していれば「凝縮度が高い」
  • メソッドもクラスのように単一の責任を持つべき
    • 変更も再利用もしやすくなる。他のクラスへの移動も簡単に
    • メソッドの役割も1文で説明できると良さそう
    • クラスが行うこと全体がより明確になる

依存関係の管理

  • 一方のオブジェクトを変更したとき、他方のオブジェクトも変更せざるを得ない恐れがあるならば、片方に依存しているオブジェクトがある
  • 一定の依存関係が2つのクラス間に築かれるのは避けられない。結局共同作業は不可欠だから
  • 依存性の注入
    • あるメッセージに応答できるオブジェクト、つまりダックタイプのオブジェクトをクラスの初期化時に要求するようにすること
  • 自身よりも変更されないものに依存しなさい
    • あるクラスは他のクラスよりも要件が変わりやすい
    • 具象クラスは抽象クラスよりも変わる可能性が高い
    • 多くのところから依存されたクラスを変更すると、広範囲に影響が及ぶ

柔軟なインターフェースを作る

  • クラスのパブリックインターフェースとプライベートインターフェース
    • パブリックインターフェース
      • そのメソッドによってクラスの主要な責任を明らかにする・他者がそこに依存しても安全etc.
    • プライベートインターフェース
      • パブリック以外のメソッド。実装の詳細に関わり、他のオブジェクトから送られることは想定されていない。変更されやすいし、他者が依存するのは危険
    • クラスの責任とパブリックメソッドは対応する。
    • 設計の目標は「今日の要求に応えるために十分なコードだけを書きつつ、将来的な柔軟性を最大限に保つこと」。良いパブリックインターフェースは、想定外の変更に対するコストを下げる
  • ドメインオブジェクト
    • アプリケーションにおいてデータと振る舞いの両方を兼ね備えた命名がされて表されるであろうオブジェクト
  • シーケンス図
    • これを書くと設計の重点が「クラスとクラスが誰と何を知るか」から、メッセージがどう送られるかに移る。
    • 「オブジェクトが存在するからメッセージを送る」のではなく、「メッセージを送るためにオブジェクトが存在している。」
  • 設計の目的は「今の役割を果たし、簡単に再利用でき、将来的な予期せぬ用途にも対応できるコードを書くこと
  • デメテルの法則
    • オブジェクトを疎結合にするためのコーディング規則の集まり
    • そこへメッセージを送ることができるオブジェクトの集合を制限する
    • 3つ目のオブジェクトにメッセージを送る際に、異なる型の2つ目のオブジェクトを介することを禁じる
  • ポリモーフィズム
    • 多岐にわたるオブジェクトが同じメッセージに応答できる能力
    • メッセージの送り手は、受け手のクラスを機にする必要がなく、受けてはそれぞれが独自化した振る舞いを提供する
  • ダックでリファクタリングできる例
    • クラスで分岐するcase文
    • kind_of?とis_a?
    • respond_to?

継承

  • 共通の振る舞いを持つものの「いくつかの面においては異なる」という強く関連した型の問題を解決する
  • サブクラスは、そのスーパークラスを「特化」したもの
  • 正しい抽象を特定するのが簡単なのは、存在する具象クラスが少なくとも3つある時
  • リスコフの置換原則
    • スーパークラスが使えるところではサブクラスが使えるアプリケーションを作れる。派生型は上位型と置換可能
  • 抽象的な部分をスーパークラスに押し上げる方式が好ましい
    • 既存のクラスの具象 → 抽象変換を、具象的な部分のみを新しいサブクラスに押し下げてやろうとすると、具象的な振る舞いの一部を誤って置き去りにする恐れがある。
    • その定義からして、残された具象的な振る舞いが全てのサブクラスに当てはまるなどということはない
  • クラスの階層構造は浅くする
    • 深い階層構造が抱える問題は、メッセージを解決するための探索パスが長くなること
    • 自身より上に位置するオブジェクト全てに依存するので、階層が深いほどに大量の依存を初めから持つことになる。しかもそれは変更される可能性がある。

テスト

  • オブジェクトが受信する、もしくは送信するメッセージに集中すべき
    • 受信メッセージは、その戻り値の状態をテストする
    • 送信コマンドメッセージは、送られたことをテストする

その他

  • わかりやすいエラーメッセージを伴って失敗するコードを書くことは、書く時点では比較的小さな努力しか要しないが価値が永遠に続く