woshidan's loose leaf

ぼんやり勉強しています

オブジェクト指向設計実践ガイド ~Rubyでわかる進化し続ける柔軟アプリケーションの育て方の7~9章を読んで

上記の本の7~9章を読んで印象に残った部分をまとめました。

7章

  • 継承の手法を使い「ロール(役割)」を共有する解決法について
  • クラスの継承などの関連がなかったオブジェクト同士が持つ共通の振る舞い「ロール」
  • 隠れたロール(役割)を明らかにし、その振る舞いをすべての担い手どうしで共有するためのコードを書いていく
    • Preparerロールの存在が示唆するのは、対応するPreparableロールの存在(対となるロールの存在)
      • Preparableのインターフェースには、Preparerが送るであろうbicycles、customers、vehicleメソッドのメッセージがすべて含まれる
      • 一般的なロールは特定のメッセージシグネチャだけではなく、特定の振る舞いも必要とするロール ... Rubyではモジュールを用いて実装
    • 共有の振る舞いをモジュールに入れる正しい方法
      • クラス名をいくつも確認したうえで、ある変数に代入する値を決定するくらいなら、変数名をメソッドにして、オブジェクトに問い合わせるようにする
      • メッセージに基づく期待はクラスの境界を超越し、ロールを明らかにする
      • オブジェクトは自身の振る舞いは自身で持つべき
      • 一つの具象クラスでまずロールの振る舞いを実装し、それを複数の具象クラスで使えるものに再構成する
  • クラスによる継承はis-aの関係、モジュールによる継承はbehaves-like-aの関係
  • ロールの設計のアンチパターン
    • typeやcategoryによってどんなメッセージを送るかselfに送るか決めている => クラスによる継承を使った方が良い
    • メッセージを受け取るオブジェクトのクラスを確認してから、どのメッセージを送るかをオブジェクトが決めている => ダックタイプを見落としてしまっている
    • 一部のクラスでしか使わないようなコードがモジュールに置かれている
    • サブクラスはスーパークラスのインターフェースに含まれるどのメッセージが来ても応えられるべきであり、同じ種類の入力を取り、同じ種類の出力を行わなければならない
      • ほかのオブジェクトに自身の型を識別させ、自身の扱いや何が期待できるのかを決めさせることは、どんなことであっても許されない
  • 継承可能なコードを書くための最も基本的なコーディングの手法は、テンプレートメソッドパターンを使うこと
    • テンプレートメソッドは、アルゴリズムのうち変化する場所を表す
  • 継承する側でsuperを呼び出すようなコードを書くのは避ける。代わりにフックメッセージを使う。

8章

  • コンポジションとは、組み合わされた全体が、単なる部品の集合以上となるように、個別の部品を複雑な全体へと組み合わせる(コンポーズする)行為
    • コンポジションにおいては、より大きいオブジェクトとその部品が、「has-a」の関係によって繋げられる
    • 個々の部品(part)は「ロール(役割)」であり、自転車自体は、そのロールを果たすほかのどんなオブジェクトとも喜んで協力する
  • コンポジションは、「hasa」関係を持ち、かつ、包含される側のオブジェクトが包含する側のオブジェクトから独立して存在し得ないものを指す -集約は、まったくもってコンポジションのようなものですが、一点、包含される側のオブジェクトの存在が独立していることが異なる
  • クラスによる継承もコンポジションも「コード構成のテクニック」
    • 「オブジェクトを階層構造に構成するコストを払う代わりに、メッセージの委譲は無料で手に入れられる」
    • コンポジションではオブジェクトを独立して存在させられる代わりに、明示的なメッセージ委譲のコストを払っている
    • 一般的なルールとしては、直面した問題がコンポジションによって解決できるものであれば、まずはコンポジションで解決することを優先
    • is-a関係に継承を、behaves-lika-a関係にダックタイプを、has-a関係にコンポジションを適用する

9章

  • 変更可能なコードを書くことに必要な3つのスキル
  • テストをすることの真の目的は、設計の真の目的がまさにそうであるように、コストの削減
    • テストから優れた価値を得るには、意図の明確さが求められる
      • テストの意図
        • バグを見つける
        • 仕様書となる
        • インターフェースに依存したテストを作ることで内部設計に関する決定を遅らせる
        • 適切なテストがなければ抽象度の高いコードベースの改修は厳しい
        • 設計の欠陥を明らかにする ... テストのセットアップに苦痛が伴うのであれば、コードはコンテキストを要求しすぎている
    • 何を、いつ、どのようにテストするかを知る必要がある
      • 論理的に考えれば、パブリックインターフェースに定義されるメッセージを対象としたものを書くべき
      • テストは、オブジェクトの境界に入ってくる(受信する)か、出ていく(送信する)メッセージに集中すべき
      • オブジェクトは、自身のパブリックインターフェースを構成するメッセージに対して「のみ」、状態についての表明を行うべき
      • 送信メッセージには、副作用がまったくないもの(クエリ)と副作用があるもの(コマンド)がある
        • クエリメッセージは、受け手のパブリックインターフェースの一部であり、必要な状態テストはすべてそちらで実装している
        • コマンド(命令)メッセージが適切に送られたことを証明するのは送り手のオブジェクトの責任
      • テストを最初に書く意味があるのならば、いつでもそうすべき
        • 適切な時間に、適切な量で終われば、テストをすることと、テストファーストでコードを書くことは、全体的なコストを下げる
      • テスティングのメインストリームに居ることには多くのメリット
        • BDDは外から内へのアプローチをとり、アプリケーションの境界でオブジェクトをつくり、内向きに入っていく。まだ書かれていないオブジェクトを用意するために、モックが必要となる
        • TDDは、内から外へのアプローチをとる。通常、ドメインオブジェクトのテストからはじまり、それらの新しくつくられたドメインオブジェクトをコードの隣接するレイヤーのテストで再利用していく
        • テストをするときには、アプリケーションのオブジェクトを大きく「テスト対象オブジェクト」とそれ以外の2つのカテゴリーに分けて考え、テストはテスト対象オブジェクト以外は可能な限り無知であるようにする
  • 受信メッセージをテストするときは
    • 使われていないメソッドを見つけたら削除する
    • パブリックインタフェースをテストする
    • テスト対象以外のオブジェクトをテストしなくていいようにオブジェクトが利用しているパブリックインタフェースを持ったロールを導入して依存オブジェクトをテスト用の簡素なもの(テストダブル)にする
      • テストダブルを使うと、本番のコードが壊れているのにテストは通る場合があるが本番のコードを作るコストとの兼ね合いで使い分ける
  • プライベートメソッドをテストするときは
    • テスト中ではプライベートメソッドを無視する
    • プライベートメソッドをそもそももたない(持ちすぎない)ようにする(そのようなクラスは単一責任の原則から外れている匂いがする)
    • 複雑なコードをリファクタリングする場合など意味がある場合に限ってプライベートメソッドのテストを書く
  • 送信メッセージをテストするときは
    • クエリメッセージはメッセージの受け手のパブリックインタフェースとしてテストされるので無視する
    • コマンドメッセージは送信したオブジェクトの責任としてテストをする
      • メッセージが問題のオブジェクトに送信されたかどうかはモックを使ってテストする
  • ロールの担い手が共有できるテストを書くには
    • (MiniTestの場合)モジュールでインターフェースに応答するかどうかのテストを書き、includeでロールの担い手間でテストを共有する
      • テストダブルと本番アプリケーションのクラスの間に同様のテクニックを用いて、インターフェースが変更された場合にテストダブルを利用している箇所が壊れていることを検出できるようにする
    • ロールの担い手にロールの利用者側からメッセージが送信されているかどうかにはモックを用いてテストを行う
  • 継承されたコードをテストするには
    • モジュールにスーパークラスが満たす性質(パブリックインタフェースが実装されていること)を書き、サブクラスのテストでincludeする
    • モジュールにサブクラスが満たす性質(特化にあたるメソッドのパブリックインタフェースが実装されていること)を書き、サブクラスのテストでincludeする
    • 具象サブクラスのメソッドをテストするときは、スーパークラスから受け継いだアルゴリズムのメソッドは無視しつつサブクラスで特化している部分のテストを行う
    • 抽象スーパークラスアルゴリズムを扱うメソッドをテストするときは、単純なサブクラスを用意して単純なサブクラスでの動作をテストする