<輪読会> オブジェクト指向実践ガイド -第4章 柔軟なインターフェースをつくる-

輪読会メンバー

  • Izumi Haruya 

github.com

  • Sekine Yutaro

github.com

第4章 柔軟なインターフェースをつくる

オブジェクト指向アプリケーションは単なるクラスの集まりではなく、メッセージによって定義されている

  • オブジェクト間で受け渡されるメッセージについて考慮すべきこと
    • オブジェクトの目的
    • オブジェクトの依存関係
    • オブジェクトが互いに、どのようなやりとりをするか

4.1インターフェースを理解する

1つ目(左)のアプリケーション

  • 1つ目のアプリケーションでは

    • メッセージに明らかなパターンはない
    • すべてのオブジェクトは、任意のメッセージを任意のオブジェクトに送れるようになっている
  • 1つ目のアプリケーションのオブジェクトでは

    • 再利用が困難
    • それぞれが自身について外部に晒しすぎている
    • 近隣のオブジェクトについても知りすぎている

どのオブジェクトの、どのメソッドであっても、その粒度にかかわらずほかのオブジェクトから実行できることが問題である。

2つ目(右)のアプリケーション

  • 2つ目のアプリケーションでは

    • メッセージには明確に定義されたパターンがある
    • オブジェクトは明確で適切に定義された方法でコミュニケーションをとっている
  • 2つ目のアプリケーションのオブジェクトでは

    • 着脱可能
    • コンポーネントのようなオブジェクトから構成されている
    • それぞれが互いに最小のことのみを明らかにしている
    • 互いについて知っていることもできる限り最小限である

上記のアプリケーションでは

  • 制約があることは明らか
  • どのメッセージがどのオブジェクトに渡せるのかについて、何らかの合意と約束ある
  • それぞれのオブジェクトは、ほかのオブジェクトが使えるようなメソッドを明確に定義できている

答えは、クラスが何を「する」かではなく、何を「明らかにする」かにあります。

4.2インターフェースを定義する

パブリックインターフェースとは

  • クラスの主要な責任を明らかにする
  • 外部から実行されることが想定される
  • 気まぐれに変更されない
  • 他者がそこに依存しても安全
  • テストで完全に文書化されている

プライベートインターフェースとは

  • 実装の詳細に関わる
  • ほかのオブジェクトから送られてくることは想定されていない
  • どんな理由でも変更され得る
  • 他者がそこに依存するのは危険
  • テストでは、言及さえされないこともある

責任、依存関係、そしてインターフェース

  • 単一の責任について
    • 単一の目的である
    • クラスの責任は、おのずとその目的を果たすこと
  • 依存関係について

    • クラスは自身より変更の可能性が低いクラスにのみ依存するべき
  • パブリックとプライベートについて

    • メソッドにパブリックやプライベートと印を付けることは、クラスの使用者に対し、どのメソッドには安全に依存できそうか、ということを伝えている
    • パブリックメソッドを使うときは、それらのメソッドが安定していると信頼していることである
    • プライベートメソッドに依存することを決めるときには、本質的に不安定な何かに依存していることを理解し、それゆえ離れたところの、関係のない変更にも影響を受けるリスクが増えることも知ったうえで行う必要がある

4.3パブリックインターフェースを見つける

  • パブリックインターフェースを見つけ、定義することは1つの技巧である

    • ただ、これが設計上難しいのは、はっきりと決められたルールがないからである
    • 「十分に良い」インターフェースをつくる方法はいくらでもあるが、「十分に良くない」インターフェースのコストはすぐに明らかになるわけでもないので、失敗から学ぶのもかんたんではない
  • オブジェクト指向設計とは 設計の目標は、例に漏れず、今日の要求に応えるために十分なコードだけを書きつつ、将来的な柔軟性を最大限に保つこと

見当をつける

最初はテストからはじめるべきだと強く思っていることでしょう。しかし、強く思ったからといってかんたんになるわけではありません。多くの設計初心者にとって、最初のテストを考えることは本当に難しいものです。最初のテストを書くためには、何をテストしたいかについての考えが必要です。

  • ドメインオブジェクトとは

    • アプリケーションにおいて「データ」と「振る舞い」の両方を兼ね備えた「名詞」を表す
    • 大きくて目に見える現実世界のものを表し、かつ最終的にデータベースに表されるものと言える
    • こだわりすぎると、無理な振る舞いをさせがち
  • より良い設計をする為に

    • オブジェクトではなく、オブジェクト間で交わされるメッセージに注意を向ける必要がある
    • これらのメッセージが、ドメインオブジェクト同様に必要なものの、はるかに見えにくいほかのオブジェクトを見つけるための手がかりとなってくれる為である

シーケンス図を使う

  • シーケンス図とは

    • 上記のような図のことをシーケンス図という(一例にすぎません)
    • 「オブジェクト」とその間で交わされる「メッセージ」のフローのこと
  • シーケンス図のメリット

    • さまざまなオブジェクトの構成やメッセージ受け渡しの体系を簡潔に実験できる
    • 思考を明確にし、ほかの人と共同作業したり意思疎通したりするための手段となる

公式の様式から外れたからといって、責められることはない

  • 基本的な設計の質問
    • 「このクラスが必要なのは知っているけれど、これは何をすべきなんだろう」から「このメッセージを送る必要があるけれど、だれが応答すべきなんだろう」へ変えることが重要である
    • オブジェクトが存在するからメッセージを送るのではありません。メッセージを送るためにオブジェクトは存在する

「どのように」を伝えるのではなく「何を」を頼む

送り手の望みを頼むメッセージと、受け手にどのように振る舞うかを伝えるメッセージの違いはささいなものに見えるかもしれません。しかし、その影響は絶大です。

適切に定義されたパブリックインターフェースを持つ、再利用可能なクラスをつくるための鍵となる部分です。

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

  • パブリックインターフェイス(振る舞い)を小さくする
    • コードのメンテナンス性の向上
    • コードの柔軟性の向上

コンテキストの独立を模索する

  • 考えられる限り最も良い状況は
    • オブジェクトがそのコンテキストから完全に独立していること
    • 依存オブジェクトの注入で解決できる

ほかのオブジェクトを信頼する

  • 図4.5では、TripはMechanicに「私は自分が何を望んでいるかを知っているし、あなたがそれをどのようにやるかも知っているよ」と伝えている

  • 図4.6では、「私は自分が何を望んでいるかを知っていて、あなたが何をするかも知っているよ」と伝えている

  • 図4.7では、「私は自分が何を望んでいるかを知っているし、あなたがあなたの担当部分をやってくれると信じているよ」と伝えている

この(図4.7)手放しの信頼が、オブジェクト指向設計の要です。これによって、オブジェクトは自身をコンテキストに縛り付けることなく、共同作業できるようになります。また、これは成長し、変化することが期待されるアプリケーションには必要不可欠です。

オブジェクトを見つけるためにメッセージを使う

TripFinderクラスが含まれるべきでしょう。図4.8が示すシーケンス図では、適切な旅行(suitabletrips)を見つける責任を、TripFinderが負っています。TripFinderは、何がどうなれば適切な旅行になるかの知識をすべて持っています。ルールを知っています。TripFinderの仕事は、このメッセージに応答するために必要なことなら何でも行うことです。TripFinderは、安定したパブリックインターフェースを提供し、また、変化しやすく乱雑な内部実装の詳細は隠しています。

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

まとめ

  • メッセージから、各クラス毎に関係性の低いメソッドを隔離する為の新たなクラスを見出す
  • 結果として、パブリックインターフェースが安定し、変化しやすく内製的な設計にすることができる

クラスが単一責任かどうかを見極める

以下のような方法がある。

  • あたかもそれに知覚があるかのように仮定して問いただすこと
  • クラス(主語)でメソッド(機能)が成り立っているか質問する
  • 最小で有用なことは良いクラスである。
  • 1文でクラスを説明してみること

4.4一番良い面(インターフェース)を表に出すコードを書く

適切に定義されたインターフェースが存在することは、それが完璧であることよりも重要である

明示的なインターフェースをつくる

  • 設計者の目的

    • 今の役割を果たすこと
    • かんたんに再利用できる
    • 将来的な予期せぬ用途にも対応できるコードを書くこと
  • 「パブリック」インターフェースに含まれるメソッドのポイント

    • 明示的にパブリックインターフェースだと特定できる
    • 「どのように」よりも、「何を」になっている
    • 名前は、考えられる限り、変わり得ないものである
    • オプション引数として、ハッシュをとる

Rubyには、関連するキーワードが3つあります。publicにprotected、そしてprivateです。これらのキーワードは、まったく異なる2つの用途で用いられます。

  • 1つ目は、どのメソッドが安定でどのメソッドが不安定かを示す
  • 2つ目は、アプリケーションのほかのところに、どれだけメソッドを見える化を制御する

railsのpublicとprotectedとprivateの違いについての参考記事


public等のキーワードを使うことのメリット

  • 「将来の」プログラマーが持つ情報よりも、いまの自分のほうがより良い情報を持っていると信じている
  • いまの自分が不安定だと考えているメソッドを、将来のプログラマーに不用意に使われることは防がなければならないと信じている

これは明らかに安定だろうというメソッドでも、定期的に変わることもあります。逆に、最初の段階では一番不安定だったメソッドでも、時間という試練を超えて存在し続けることもあります。制御できるという幻想が快適であるなら、気にせずキーワードを使ってください。しかし、何人ものかなり実力のあるRubyプログラマーが、あえてキーワードを省略し、代わりにコメントやそれ用の命名規則を使い、インターフェースの「パブリック」と「プライベート」な部分を示しています(たとえばRubyonRailsでは、プライベートメソッドの先頭に_を付けます)。

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

まとめ

  • メンテナンスの方向性を示すもの(現段階での解釈)

ほかのパブリックインターフェースに敬意を払う

彼らは、時空を超えてどのメソッドになら依存可能なのかを、必死に伝えようとしているのです。彼らが付けたpublicとprivateの違いは、「いまの自分」を助けようと意図されたものであり、それに真剣に耳を傾けることに越したことはないでしょう。 もし自身の設計が、ほかのクラスにあるプライベートメソッドの使用を強制するものである場合、まずはその設計を再考しましょう。努力を注ぐことによって、代案を発見することは可能です。代案を見つけるために懸命に努力すべきでしょう。

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

外部フレームワークへの依存は、技術的負債の一形態になる可能性があるので、そのような依存は避けるべきである

プライベートインターフェースに依存するときは、注意深く

プライベートメソッドの使用を避けられないとしても、アプリケーションの多くの場所で参照されることは防げます。プライベートインターフェースへの依存は、リスクを高めます。依存を隔離することによって、このリスクを最小限にとどめましょう。

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

コンテキストを最小限にする

パブリックインターフェースを構築する際は、そのパブリックインターフェースが他者に要求するコンテキストが最小限になることを目指す

  • 「何を(what)」と「どのように(how)」の違いを常に念頭に置き、パブリックメソッドをつくる際は、メッセージの送り手が、クラスがどのようにその振る舞いを実装しているかを知ることなく、求めているものを得られるようする
  • 最初に書いた人がパブリックインターフェースを定義しなかったからといって、遅すぎることない、自身の手でパブリックインターフェースをつくることも遅くない

4.5デメテルの法則

  • デメテルの法則(LoD:LawofDemeter)
    • オブジェクトを疎結合にするためのコーディング規則の集まり

デメテルを定義する

  • デメテルとは

    • メッセージを「送る」ことができるオブジェクトの集合を制限する
    • 3つ目のオブジェクトにメッセージを送る際に、異なる型の2つ目のオブジェクトを介すことを禁じる
    • 「直接の隣人にのみ話しかけよう」だとか、「ドットは1つしか使わないようにしよう」なんて言い方もされる
  • デメテルの法則を考える為の具体例

    • customer.bicycle.wheel.tire
    • customer.bicycle.wheel.rotate
    • hash.keys.sort.join(',')

      行はそれぞれ、いくつものドット(ピリオド)を含むメッセージチェーンとなっています。これらのチェーンは、形式張らない表現では「列車事故(trainwrecks)」と言われます。メソッド名はそれぞれ列車の車両を表し、ドットが連結部を表します。これらの車両は、デメテルの法則に違反している可能性を示しています。

  • デメテルの法則のポイント

    • .(ドット)を使用してメソッドチェーンをつなげることは場合によっては良いとされる
    • 複数のクラス(オブジェクト)をメソッドチェーンでつなげることはデメテルの法則に違反する

法則を違反することによる影響

デメテルの法則が「法則」になったのは、人間がそう決めたからです。この大げさな名前にだまされてはいけません。法則といっても、これは「毎日歯を磨くべきです」といった規則のようなもので、万有引力の法則とは異なります。歯医者には言いにくいかもしれませんが、たまに破ったところで、世界が崩壊することはありません。第2章では、コードは「見通しが良い(transparent)」もの、「合理的(reasonable)」なもので、「利用性が高い(usable)」もの、「模範的(exemplary)」なもの(TRUE)であるべきだと述べました。上記のメッセージチェーンには、これにかなっていないものがあります。

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

違反を回避する

  • 違反を回避する方法
    • メッセージチェーンに具体化されていた知識を、カプセル化(委譲する)

      注意しなければ、デメテルの法則にかたちだけ従い、その主旨には反するコードになってしまう可能性があります。委譲を使って強固な結合を隠したからといって、コードの結合を解いたことにはなりません。

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

デメテルに耳を傾ける

  • デメテルの法則の注意点
    • 「もっと委譲を使いましょう」ということではない
    • 既知の知識(ドメインオブジェクト)を疑うことから始める

デメテルに耳を傾ける、ということは、自身の視点に注意を払うということです。

4.6まとめ

メッセージに集中することによって、見過ごされていたオブジェクトが明らかになるかもしれません。メッセージが、受け手がどのように振る舞うかを伝えるものではなく、相手を「信頼」し送り手が望むことを頼むものであるとき、オブジェクトは予期しなかった方法や、まったく新しい方法で再利用できる、柔軟なパブリックインターフェースを自然に進化させていくでしょう。 くでしょう。

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

参考書籍

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