追記: 現在は推奨していません。
デザインパターン「Nullオブジェクト」について解説します。加えて、後半でNullオブジェクトパターンに関する私見を述べます。
なぜ「Nullオブジェクト」が必要か
C や C# などのいくつかの言語に null という概念があります。null
はしばしば「何もない」や「失敗」といった 特殊な状態 を表す値として、null でない値と混ぜて使われますが、null
と非 null の差異は次のようなやっかいな状況の引き金となります。
null
と非 null の違いの一つに、備えるメソッドの種類があります 1 。例えばプログラミング言語 C# では、null
のメソッドを呼ぼうとすると、例外が投げられます。
// 何らかのクラス X
public class X
{
// オーバーライド可能なメソッド
public virtual void SomeMethod()
{
// ...
}
}
public void ThrowNullReferenceException()
{
// 型 X の変数 x を null にする。
X x = null;
// x (= null) のメソッドを呼ぶ。例外が投げられる。
x.SomeMethod();
}
そのため、プログラムの処理中にエラーが起きると困る場合、 null
のメソッドを呼んでしまわないようにする必要があります。
このような事情から、「null
でない場合だけ、メソッドを実行する」(null
の場合は何もしない)ようにしたいことがしばしばあります。
public void NotThrowNullReferenceException()
{
// 型 X の変数 x を null にする。
X x = null;
// x が null でない場合だけ、メソッドを呼び出す。
if (x != null)
{
x.SomeMethod();
}
}
しかしメソッドを呼ぶ際にいちいち if 文を書いていると、 if 文が大量に発生してしまいます。コードが増えるだけならまだしも、if 文を書き忘れて、意図せぬ例外を投げてしまうおそれもあります。
このような状況を回避する方法の1つが、 Nullオブジェクト です。
Nullオブジェクトの作りかた
Null オブジェクトとは、次のように定義される、「null
の代わりに使われるオブジェクト」のことです。
// X を継承した新しいクラス
public class NullX
: X
{
// 「何もしない」メソッドとしてオーバーライドする。
public override void SomeMethod()
{
}
}
そして、型 X の変数には、null
の代わりにこの NullX クラスのインスタンスを代入するようにします。これにより、変数が null
ではないと分かるので、条件分岐を行わずとも安全にメソッドを呼び出せるようになります。
public void NotThrowNullReferenceException()
{
// 型 X の変数 x を (null ではなく) NullX のインスタンスにする。
X x = new NullX();
// x は null でないと分かっているので、安心してメソッドを呼べる。
x.SomeMethod();
}
めでたし、めでたし。
nullの発生源
(ここからは私見になります。)
ところで、そもそもなぜ、型 X の変数 (仮に x
と呼ぶ) に null
が入ってしまったのでしょうか。先ほどのサンプルでは、話を単純にするために x
を使用する直前に X x = null;
と書いていましたが、実際にはこんなコードはありえません。x == null
になるには理由があるはずです。
C# の場合、理由は2つ考えられます:
x
は初期化されなかった。(C# では、初期化されていないx
の値はnull
になります。)- 誰かが
x
にnull
を代入した。あるいは、誰かがx
の初期値としてnull
を指定した。
前者の場合
前者の場合、「x
が初期化されないまま使用されている」ことになります。
ここで確認する必要があるのは、「x
が初期化されないまま使用されることがあると 開発者が理解している かどうか」です。理解していないのなら、 x == null
で条件分岐するのではなく、 x
が必ず初期化されるようにコードを修正するべきです。なぜなら、「なぜ x == null
になるのか分からないけれど、とりあえず x == null
の場合は何もしないようにしておく」という考えは、null
のメソッドを呼び出すことで起こるはずだった例外を「握りつぶす」ことと等価だからです。
一方、理解しているのであれば、それは x
の初期化時に null
を設定しているのと同じことですので、後者の場合に含まれると考えてよいでしょう。
後者の場合
後者の場合、null
を設定した人は、どういう意図をもってそれをしたのでしょうか。言い換えると、x == null
である場合に、コードがどう動くことを期待しているのでしょうか。
もし「x == null
の場合には、x
を使用するコードは、何もしないべき」だと考えていたなら、そもそも x
に null
を設定するべきではありませんでした。なぜなら、 null
は「何もしない」を意味するものではない からです。本来の意図を正しく表現するには、x
に「何もしないという挙動を持つインスタンス」を設定すべきなのです。
// X の派生クラスで、「何もしないという挙動」を持つもの
public class NullX
: X
{
public override void SomeMethod()
{
// 何もしない。
}
}
これがさきほどの NullX
クラスです。名前に null とついてはいますが、null
とは異なり、実体を持つ何かです。
まとめ
- 問題:
null
のメソッドを呼んでしまわないように、条件分岐を書きすぎたり書き忘れたりしてしまう。 - 解決策:
null
を使わない。変数は適切なインスタンスで初期化する。
参考
-
Ruby によるサンプルコードと、Nullオブジェクトパターンの参考になるリンクが記載されています。
-
null の性質は言語によって異なります。例えば null に対して任意のメソッドを呼べるような言語に対して、本記事の話は無意味です。 ↩︎