こんにちは。
今日はメッセージの話をしようと思います。
大量のメッセージと小さなメソッドの 利点をお見せします。
多くの開発者は 反対なことを信じているようですが。
このスライドはこの講義での 設計に関することを示しています。
Pharo だけでなく
あらゆるオブジェクト指向言語での 設計で有効なことです。
知っての通り、メッセージ送信は 一種のフックで
その振る舞いを定義する 場所になっています。
開発者はよく大きなメソッドを好みます。
そのほうが理解しやすいと言います。
行を読んでコードを理解します。
しかし、この講義では
それは褒められたものではない ということをお見せします。
一般に大きなメソッドは悪い設計です。
クラス階層や
複数クラスでの同じメソッドの定義で
場合分けを定義することができます。
もし、あるクラスに大量の操作が 定義されて、肥大化していて
状態に応じて適切な操作を 選ばなければならないとしたら
「この状態の場合にはこの操作を実行して
この状態のときにはあの操作を実行する」 のようなコードを書くことになります。
結果、長大なコードに
if があちこちに溢れかえります。
つまり、新しい場合分けが必要な時に
色々なメソッドのあちこちを 修正しなければならないということです。
右側の設計では
それぞれの場合についてクラスが対応付けられて 操作が定義されています。
操作をおこなうメッセージを オブジェクトに送るだけで
条件分けがされます。
プログラマが if を書く必要はありません。
多態性の原理によって 自動的に行われます。
次のスライドでは、1 つのプログラムを
小さく分割することで改善する例を お見せします。
ここに巨大メソッドがあります。 理解しにくくて
多くのことを行います。
私がやりたいのは、これです。
サブクラスで
この変数に異なる値を与えます。
つまりこういうことです。
サブクラスを作って
全てのコードをコピーして 同じコードを持たせて
それぞれについて修正を加えます。
Java のような
private というキーワードを使う言語では
メソッドで使われる属性が private の場合
こうするのは不可能です。
そのメソッドが private な インスタンス変数を使っていると
そのサブクラスは同じコードを使えません。
いずれにせよコードの複製は 基本的に良くないのです。
バグも複製してしまいます。
元のメソッドにバグがあったら その複製にもバグができることになります。
そして一方を修正したら 全ての複製について修正する必要があります。
開発者としては 余計な仕事をすることになります。
しかも、色々なコピーの箇所を 覚えておく必要があるわけです。
正しい解決策はメッセージ送信です。
対応するメソッドの中身を 直接埋め込んで書くのではなく
メッセージを送ることで
サブクラスがその振る舞いを オーバーライドすることができます。
bar メソッドを見ると
self に foo を送っています。
クラス A では foo は 10 を返しますが
サブクラスはこの値をオーバーライドして
例えば 42 に置き換えることができます。
今お見せしたコードを改良して
複製ではなく継承機構を使うには
どうしたら良いでしょう？
この部分を抽出して メッセージ送信にします。
メソッドの抽出という リファクタリング機能でできます。
ツールがこのコードをこのコードに 変換してくれます。
前のスライドで選択したコードが
ratio という新しいメソッドとして 括り出されています。
そしてコードが書かれていた部分は メッセージ送信になっています。
つまり、サブクラスでは
この振る舞いを変えることができる ということになります。
完全に変えてしまっても良いですし
スーパークラスの振る舞いを利用して 修正するのでも良いです。
今やったことは
super に ratio メッセージを送って
スーパークラスにあるこのコードを実行して
その結果に10を加えます。 これで元の目的を達成できます。
もう 1 つの方法は この部分を抽出して
サブクラスがその振る舞いを 変更できるようにすることです。
このコード片を
メソッドにします。
そして元のメソッドでは メッセージを送ります。
この場合
クラスがハードコードされています。
つまり、サブクラスが UINode クラスの サブクラスなど
別のクラスを使いたい場合に
メソッド全体を複製しなければ ならなくなるでしょう。
ここで同じ方法で
クラスをメソッドに括り出して
サブクラスがそれを 変更できるようにします。
それがこれです。
メソッドにしたい部分を括り出して
メッセージを送ります。
メッセージを送ることで サブクラスがその振る舞いを変えることができます。
前に言ったように プログラマーの中には
このやり方に同意しない人たちもいます。
細かく散らばったメソッドを読むのは 難しいと言います。
それよりも大きなメソッドを 1 行ずつ読むのうが良いと言います。
これは実践として良いやり方ではありません。 アプリケーションが大きくなると
1 行ずつ読んでも解らなくなるからです。
1 行ずつ読んでも全体で何が起こるのか 把握することはできません。
これこそ抽象を使うべきところであり
メソッドから一部を括り出すことの 意味があります。
個々の詳細を理解しなくても メソッド全体としての意味を理解できます。
したがって、小さなメソッドは優れています。 いつでもそうすべきなのです。
では続けましょう。
ここに 55 という値が
メソッドにハードコードされています。
そのためにサブクラスは 例えば 100 に変更できません。
そこで前にやったように この値を括り出して
別のメソッドにすることで サブクラスが値を変えることができます。
このやり方のもう 1 つの利点は
ここに 55 という値が 書いてありましたが
それを self averageRatio とすることで
数値ではなく名前を示していることです。
これで 55 という値が何なのか 知ることができます。
これで 55 は averageRatio だと コードを読んで理解できるわけです。
このように、小さなメソッドは コードの読解の助けになります。
55 を返す averageRatio メソッドを 定義することで
サブクラスがその値を変えることが できるようになります。
では、クラスのユーザーがその値を 変えたい時はどうしたら良いでしょう？
考えられる方法としては
インスタンス変数を使います。
前のスライドの averageRatio メソッドが
インスタンス変数の値を 返すようにします。
インスタンス変数に値が設定されていなければ デフォルト値を返します。
デフォルト値は 55 です。
これでのオブジェクトのユーザーは
好きなように値を設定できます。
この設計では
サブクラスは defaultAverageRatio をオーバーライドして
値を変更することができます。
そしてこのクラスのユーザーは 値を設定することができます。
この手法はグリュイエール（チーズ） 指向プログラミングと呼ばれます。
というのも、オブジェクト指向の プログラムやメソッドは
フックという穴を持っていて
その穴をサブクラスが 埋めることができるからです。
まとめると
コードはサブクラスで再利用され 再定義されます。
メッセージを送るたびに
サブクラスにはスーパークラスの振る舞いを 変える機会を得ます。
修正しても完全に変えても構いません。
メソッドを小さくすることは良いことです。 それによって細かな式に名前を与え
サブクラスにその式を変更する自由を 与えるからです。