みなさん、こんにちは。 反 if キャンペーンについてお話します。
特に、説明したいのは
返り値の nil や nil チェックが いかに良くないかです。
ここに例題コードがあります。 オブジェクト的ではありません。
メソッドの引数は動物です。
動作はその動物によって変わります。
例えば、犬に尻尾を振らせたり アヒルにガーガー鳴かせたりします。
猫には他のことをさせます。
さて、どうして if 文を使うのは 問題があるのでしょう？
特に、レシーバーの型を 検査する場合です。
例えば、ここに新しい動物を 追加したいとします。
当てはまる可能性がありそうな if 文を探して
プロジェクトコード全体を チェックする必要があるでしょう。
プロジェクトを通して大量のコードを 書き換えなければならなくなります。
さらに、メソッドの場合分けを 追加することは厄介で
瑣末なことに 追い立てられてしまいます。
動物を追加すると このメソッドが長大になって
それぞれの動物の種類も 細くなっていくでしょう。
例えば犬に尻尾があるというような 単純な特徴であっても
それぞれに場合分けを 作らなければならなくなります。
コードがどんどん煩雑になり 読みにくくなっていきます。
だから、そんなことはやめて メッセージを送ります。
何度も繰り返してきたポイントです。
覚えるべきキーポイントは メッセージを送ることです。
先ほどのコードは このように置き換わります。
それぞれの関係するクラスに showHappiness メソッドがあり
各クラスがそれぞれの動物が どう喜びを示すのかを決めます。
どんな動物であれ、やることは メッセージを送ることだけです。
動物に showHappiness を送ります。
そして 1 つのメソッドが 実行されます。
Pharo は if という機能を 追い出そうとしているということです。
動物の種類に応じてどのメソッドを 適用するのか Pharo が決めます。
これが自動的に実行されます。
オブジェクトの種類ごとに if を指定する必要はありません。
そんなことをするとコードの 統一性とダイナミズムが減ります。
では、その特殊なケースである nil について議論します。
あるメソッドが nil を返すと
クライアントは if を 使わざるを得なくなります。
if は褒められたものではない のに反して。
パラメータと Interencer を使った
例題コードがあります。
コードの種類は重要ではありません。
ここを見ればわかるように 場合によって nil が返されます。
つまり、このコードを使う時は
メッセージの返り値を チェックする必要があります。
rulesForFact: が nil を返したかどうかを。
応答によって異なることをします。
この場合には
複数形を使っているので
このメソッドはおそらく コレクションを返すのでしょう。
nil を避ける効果的な解決策は
この場合は空のコレクションを 返すことです。
これで大丈夫な場合が多くあります。
nil を返さずに空のコレクションを返すことで コードがシンプルになります。
クライアントは単にそのコレクションを 列挙するだけだからです。
もし空だったら何もしません。
例外を使うケースもあります。
ファイルストリームがあって
書き込みモードで open しておらず エラーにする場合には
nil を返すかわりに 例外をあげてシステムに知らせます。
Pharo ではこれを 「例外を申し立てる」と呼びます。
Exception クラスかそのサブクラスの インスタンスを生成して
signal メッセージを送ります。
こうすることで、問題がありそうな時に nextPutAll: メソッドのクライアントに
返り値が nil かどうかを チェックせずに済みます。
クライアント側や クライアントのクライアントなどが
その例外を処理します。
特定のレベルで集中して 例外を捕まえることができます。
これで if の使いすぎを避けられます。
他にも、インスタンス変数の値が 初期化されているかどうかを
確認するための nil チェックも 見かけます。
変数がまだ nil であれば 何らかの反応をするようなコードは
すぐに初期化して
あらゆる場合に対応できる値に したほうが良いです。
つまりここでは
members は コレクションを格納しますが
nil ではなく空のコレクションで 初期化します。
これもまた うまくいく場合が多くあります。
変数に初期値を与えたいけれども
その初期値を計算するのに 大きなコストがかかるようならば
初期値を計算するのをギリギリまで 遅らせることができます。
計算しなくて済む場合もあるので 実行時間の節約になります。
そんな場合には 遅延初期化を使います。
値が必要とされた時に初期化をします。
もし値がまだ nil であれば 値を代入します。
もし値がもう nil でなければ その値をそのまま返します。
ここに nil と if がありますが 1 つしかありません。
この変数を使う人は この decent メソッドを使うことで
nil かどうかチェックする 必要がありません。
時々あるのですが
反応する必要があるかどうか チェックする必要があります。
この例題のような場合です。
ここに ToolPalette があります。
もしツールが選択されていたら 反応します。
もし選択されていなければ 反応しないでおきます。
もし selectedTool が nil を返したら
ツールが選択されていないので 何もする必要がありません。
selectedTool が何かを返したら
それに対して何かするように 頼みます。
これの代わりになる良い方法は
NullObject パターンを使うことです。
ツールのあるなしで 2 つの場合に分けるのではなく
ツールの 1 つは何もしないツールにして 1 つの場合にします。
このツールは デフォルトで選択されています。
アクションの依頼に対して 何もしないツールを作るのです。
ツールを選ばないのではなく 何もしないツールを選ぶのです。
NullObject についてもっと知りたければ これらの文献を読んでください。
まとめます。
メッセージは if よりも有効です。
if を使う場合もあるでしょうが
if を避けて代わりにメッセージを送る ことができる場合も多くあります。
nil を返すのは避けましょう。 返り値が nil かチェックするための
if を入れなければならなくなります。
初期化は生成時に行うか あるいは遅延初期化を使いましょう。
デフォルトまたは何もしない振る舞いを 表すオブジェクトを作りましょう。
これは Pharo だけでなく あらゆるオブジェクト指向言語で有効です。
どんな言語を使うにせよ、これらの ポイントを覚えておくことが大事です。