<輪読会> オブジェクト指向実践ガイド -第5章ダックタイピングでコストを削減する-

輪読会メンバー

  • Izumi Haruya 

github.com

  • Sekine Yutaro

github.com

第5章ダックタイピングでコストを削減する

  • はじめに

    オブジェクト指向設計の目的は変更にかかるコストを下げることです。メッセージが設計の中心にあることはもうご存じのことだと思います。また、いまとなっては、厳密に定義されたパブリックインターフェースを構築したくなっていることでしょう。この2つの考えを組み合わせることで、強力な設計テクニックを得られます。それにより、コストをさらに削減できるでしょう。このテクニックは「ダックタイピング」として知られています。ダックタイプはいかなる特定のクラスとも結びつかないパブリックインターフェースです。クラスをまたぐインターフェースは、アプリケーションに大きな柔軟性をもたらします。

Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.2634). Kindle 版.

ダックタイプによって型付けされたオブジェクトは、さながらカメレオンのようです。

目的はもちろん、アプリケーションの柔軟性と変更のしやすさを高めることです。

5.1ダックタイピングを理解する

プログラミング言語での「型(type)」という用語は、変数の中身の分類を示すために使われます。手続き型言語では、少数の、決まった数の型があらかじめ用意されていて、一般的に「データ」の種類を記述するために用いられます。変数の中身の分類についての知識、つまり型についての知識があれば、アプリケーションはその中身の振る舞いを予想できます。アプリケーションは、数値は数式の中で利用できる、文字列は結合できる、配列は添字でアクセスできる、というようなことを、上手に仮定できます。

Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.2646). Kindle 版.

  • オブジェクト指向設計において重要な事(再確認)

    • クラスは、オブジェクトがパブリックインターフェースを獲得するための1つの方法でしかない
    • 重要なのは、オブジェクトが何で「ある」かではなく、何を「する」か
  • ダックタイプの重要性

    • クラスをまたいだ型を認識すること、そしてそのパブリックインターフェースを、「第4章柔軟なインターフェースをつくる」でつくったクラス内の型のものと同じくらい、意図を持って入念につくること
    • つまりダックタイプのパブリックインターフェースは、契約を表すものです。この契約は明示的で、適切に文書化されたものでなければならない
  • 5章で検討すること(想定) ダックタイプを説明する最善の方法は、ダックタイプを使わない場合どうなるかを検討することです。

ダックを見逃す

このprepareメソッドは、Mechanicクラスに対しての明示的な依存こそしていませんが、prepare_bicyclesに応答できるオブジェクトを受け取る、ということには依存しています。この依存はとても基本的なことなので、かんたんに見落としやすく、軽視してしまいがちですが、それでも存在することには変わりありません。Tripのprepareメソッドは、この引数が自転車を準備してくれるものを持っていると、固く信じています。

Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.2695-2700). Kindle 版.

問題を悪化させる

  • 現状の問題点
    • prepareメソッド内の新たな依存を数えてみましょう。このメソッドは特定のクラスに依存していて、ほかのクラスは役に立たない
    • これらのクラスの具体的なメソッド名に依存しており、それぞれのクラスが理解するメッセージの名前を、それが必要とする引数と共に知っている
    • この知識はすべてリスクを上げるものです。たくさんのかすかな変更が、このコードに副作用をもたらすことがある
    • この手のコードは増殖してことがある傾向にある

ダックを見つける

依存を取り除くための鍵となるのは、「Tripのprepareメソッドは単一の目的を果たすためにあるので、その引数も単一の目的を共に達成するために渡されてくるということを認識すること」です。どの引数も同じ理由のためにここに存在し、その理由自体は引数の背後にあるクラスとは関係しません。

Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.2771-2774). Kindle 版.

この想定によって、形勢はきれいに一変します。既存のクラスから思考を解き放ち、ダックタイプを生み出すことができたのです。

Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.2783-2784). Kindle 版.

以下に新しい設計のコードを載せます。prepareメソッドは、引数が複数のPreparerであることを想定するようになっています。また、それぞれの引数のクラスは、新しいインターフェースを実装しています。

Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.2800-2802). Kindle 版.

# 旅行の準備はより簡単になる
class Trip
    attr_render :bicysles, :customaers, :vehicle
    
    def prepare(preparers)
        preparers.each {|preparer|
            preparer.parepare_trip(self)}
    end
end
    
# 全ての準備者(Preparer)は
# 'prepare_trip'に応答するダック
class Mechanic
    def prepare_trip(trip)
        trip.bicycles.each {|bicycle|
            prepare_bicycle(bicycle)}
    end

    # ...
end

class TripCoordinator
    def prepare_trip(trip)
        buy_food(trip.customers)
    end
    
    # ...
end

class Driver
    def prepare_trip(trip)
        vehicle = trip.vehicle
        gas_up(vehicle)
        fill_water_tank(vehicle)
    end
    
    # ...
end

# Sandi Metz. オブジェクト指向設計実践ガイド
# ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方

このprepareメソッドは、新しいPreparerを受け入れる際に、変更が強制されることはありません。また、必要に応じて追加のPreparerをつくるのもかんたんです。

Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.2816-2819). Kindle 版.

ダックタイピングの影響

  • 具象(手続き型)と抽象(オブジェクト指向)のトレードオフ

    • 具象性

      • コードを理解しやすくしてくれる
      • 拡張する際に注意が必要
      • 上記の理由から拡張の際のコストがかかる
    • 抽象性

      • コードを理解するのが難しい
      • コードを理解してしまえば、拡張するのが容易である
      • 上記の理由から拡張の際のコストを抑えられる
  • ダックタイプとは

    オブジェクトを、そのクラスではなくあたかも振る舞いによって定義されているかのように扱えるようになれば、表現力と柔軟性を備えた設計という、新たな領域に足を踏み入れたも同然です。

Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.2836). Kindle 版.

Column - ポリモーフィズム -

  • 一般的な定義

    • 「Morph」とは、ギリシャ語でform、つまり形態を意味する
    • 「morphism」は、形態を持っている状態
    • 「polymorphism」とは、多数の形態を持っている状態
  • オブジェクト指向プログラミングでのポリモーフィズム

    • 多岐にわたるオブジェクトが、同じメッセージに応答できる能力を指す
    • メッセージの送り手は、受け手のクラスを気にする必要がなく、受け手は、それぞれが独自化した振る舞いを提供する

ポリモーフィズムを使うとき、すべてのオブジェクトが適切に振る舞うことを確実にするのは、プログラマーです。このことについては、「第7章モジュールでロールの振る舞いを共有する」にて扱います。

ポリモーフィズムとは 参考記事

5.2ダックを信頼するコードを書く

設計上で難しいことは、ダックタイプが必要であることに気づくことと、そのインターフェースを抽象化することです。

Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.2871). Kindle 版.

隠れたダックを認識する

  • クラスで分岐するcase文

    • クラスで分岐するcase文の場合は、何か共通のものを共有しているに違いないとわかります。
    • 上記に気づく方法

      コードを調べ、「prepareがその引数のそれぞれから望むものは何だろうか」と、自身に問いかけましょう。その問いへの答えに、送るべきメッセージが示されます。このメッセージから、根底にあるダックタイプが定義されはじめます。

  • kind_of?とis_a?

  • responds_to?

ダックを信頼する

デメテルの法則を違反するのと同じように、この形式のコードは何かオブジェクトを見逃していることを示します。まだパブリックインターフェースを発見できていないオブジェクトを見逃しているのです。見逃しているオブジェクトが、具象クラスではなくダックタイプであるという事実は、まったく重要ではありません。重要なのはそのインターフェースであり、インターフェースを実装するオブジェクトのクラスではないのです。

Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.2928-2929). Kindle 版.

上記の引用の要約

  1. パプリックインターフェイス(メソッドの集合(メッセージ))からなるオブジェクト(クラス)を見逃している
  2. 見逃しているオブジェクトは、クラスかダックタイプ(オブジェクトを、そのクラスではなくあたかも振る舞いによって定義されているかのように扱えるメソッド的な集合?)かは重要でない
  3. 重要なのは、そのインターフェース(メッセージ)であり、インターフェースを実装するオブジェクト(クラス自身)ではない

問題のあるコードが想定していることに集中し、その想定をダックタイプを見つけるために使いましょう。ダックタイプをつかめたら、そのインターフェースを定義し、必要なところで実装します。実装したら、それが正しく振る舞ってくれると信頼しましょう。

Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.2934-2936). Kindle 版.

上記の引用の要約

ダックタイプを文書化する

最も単純なダックタイプは、単にパブリックインターフェースの取り決めとしてだけ存在するものです。

このPreparerダックタイプとそのパブリックインターフェースは、設計上では具象的な部分ですが、コード上では仮想的な部分です。Preparerは抽象的です。抽象的であるというのは、設計の道具としてはPreparerに強さを与えますが、まさにその抽象性が、ダックタイプをコード上では明白とは言えないようなものにしてしまいます。ダックタイプをつくるときは、そのパブリックインターフェースの文書化とテストを、両方ともしなければなりません。幸い、優れたテストは最高の文書でもあります。ですから、すでに半分は終わっているようなものでしょう。あとはテストを書きさえすれば、両方とも終わりです。

Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.2944). Kindle 版.

ダック間でコードを共有する

ダックタイプを実装するクラスは振る舞いもいくらか共有する必要があるときがたびたびあることに気づきます。コードを共有するダックタイプを書くことについては、第7章で取り扱う。

賢くダックを選ぶ

  • ダックタイプにもしなくていいパターンがある
    • Rubyが提供しているコアクラスを使用して、クラス間を跨いだ処理の場合は、変更を強制するかたちで変わる可能性は、極端に低いから実装(そのまま)にしても良い

      理由は開発のコスト(可能性)を下げることが目的だから

余談ですが、コアクラスを変更できるモンキーパッチという方法があるが、これをうまく使用することは大切。だが、慣れないうちは変更することが危険であるので、その正当性を説明できるような場合のみ変更を加える

5.3ダックタイピングへの恐れを克服する

5.3では動的型付けと静的型付けの比較をしていく

静的型付けによるダックタイプの無効化

静的型付け言語は、それぞれの変数の型と、メソッドのすべてのパラメーターを明示的に宣言することを求めます。動的型付け言語は、この要求を省きます。

Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.3002). Kindle 版.

  • 静的型付けの信念
    • コード内でオブジェクトのクラスを検査しがちである
    • 上記によって、型付けに縛られ、ダックタイプの利用を不可能にしてしまう

ダックタイプは、コードが安全に依存できる安定した抽象を明らかにする

静的型付けと動的型付け

  • 静的型付け言語の特性

    • コンパイラコンパイル時に型エラーを発見してくれる
    • 可視化された型情報は、文書の役割も果たしてくれる
    • コンパイルされたコードは最適化され、高速に動作する
  • 静的型付け言語の強み

    • コンパイラが型を検査しない限り、実行時の型エラーが起こる
    • 型がなければプログラマーはコードを理解できない。プログラマーはオブジェクトのコンテキストからその型を推測することができない
    • 一連の最適化がなければ、アプリケーションの動作は遅くなりすぎる
  • 動的型付け言語の特性

  • 動的型付け言語の強み

    • アプリケーション全体の開発は、コンパイル/makeのサイクルがないほうが高速
    • 型宣言がコードに含まれないときのほうがプログラマーにとって理解するのがかんたん。そのコンテキストからオブジェクトの型は推測できる
    • メタプログラミングは、あることが望ましい言語機能

メタプログラミング(Metaprogramming)とは、コンピュータプログラムそのものをデータのように扱えるプログラミング技術を意味している。 プログラムによって新たなプログラムを生成し、既存プログラムを閲覧・分析・修正・変形・拡張できることがメタプログラミングである。

動的型付けを受け入れる

一定のアプリケーションにおいては、動的型付けでの実装よりも、上手に最適化された静的型付けのコードのほうが優れた性能を発揮します。動的型付けのアプリケーションにおいて、頑張っても十分な実行速度を実現できないのであれば、静的型付けが代案となります。どうしても実装速度を上げたいのであれば、静的型付けにするしかないでしょう。

文書としての型宣言の価値についての議論は、もっと主観的です。動的型付けの経験が豊富な人にとっては、型宣言は気を散らす存在です。静的型付けに慣れている人にとっては、型情報がないと混乱するかもしれません。静的型付け言語(たとえばJavaC++)から入って、Rubyに明示的な型宣言がないことで港に繋がれた船の綱を外されたように感じる人は、なんとか持ちこたえてください。いくつもの事例証拠があります。一度慣れてしまえば、煩雑さの少ないこちらの構文のほうが読みやすく、書きやすく、理解しやすいと思うでしょう。

Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.3036-3039). Kindle 版.

  • 静的型付け言語はパフォーマンスの部分では確かに有用である
  • 静的型付けの前提

    • コンパイラは不慮の型エラーから本当に救って「くれる」
    • コンパイラの助けなしでは、これらの型エラーは「起こる」

      コンパイラコンパイルを実行するだけで、型エラーを防ぐのはプログラマー自身の知力に任されています。コードの良さは結局テストの良さになります。実行時のエラーは依然起こり得ます。静的型付けによって安全になるという考えは、確かに心地良いかもしれませんが、幻想です。

    Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.3067-3070). Kindle 版.

  • 静的型付け言語は、ダックタイプが使えないという欠点がある

    ダックタイピングは、動的型付けの上に成り立ちます。ダックタイピングを使うためには、この動的さを受け入れる必要がある

    Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.3079-3081). Kindle 版.

5.4まとめ

メッセージこそが、オブジェクト指向アプリケーションの中心にあるものです。メッセージはパブリックインターフェースを介し、オブジェクト間で交換されます。ダックタイピングは、これらのパブリックインターフェースを特定のクラスから切り離し、何であるかではなく何をするかによって定義される、仮想の型をつくります。ダックタイピングは、根底にある抽象をあらわにします。ダックタイピングを使わなければおそらく不可視だったものです。これらの抽象に依存することによって、リスクは低減され、柔軟性は高まります。アプリケーションのメンテナンスコストは下がり、変更もよりかんたんになります。

Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition) (Kindle の位置No.3081-3087). Kindle 版.

参考書籍

オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 | Sandi Metz, 髙山泰基 | 工学 | Kindleストア | Amazon