読者です 読者をやめる 読者になる 読者になる

いかなるときもnullを返してはいけないのか?

nullを返すのはオブジェクト指向じゃないよ、というのを耳にしたので。
前置、考えなしに返すものがないのでnullを返す、ということに賛成しません。
nullは異常値ではなく、正常値として扱われるケースもある、と想定しています。
想定言語は一応Javaで。
C++だと返さざるを得ないケース、パフォーマンスクリティカルな箇所で事前にnullチェック関数を置くなど無駄、というケースなどあるため。


id:JavaBlack氏が書いておられるように、

こういう話はあるかもしれない.特に「nullを返すな」なんてのはこのパターンだ.
...
そもそもJavaのように例外機構のある言語では,真の意味で「異常」を表すのは例外であって戻り値ではない.trueだろうとfalseであろうと,戻り値を返すのは「正常終了」だ.
...
長さ0配列や空文字列,その他「何もしないオブジェクト」を返すなどの基本的テクニックがある.

「最後行ではtrueを返すよう作れ」? - カレーなる辛口Javaな転職日記

ということで、僕も


Hoge[] getHogeList(); のように配列を返すならばnew Hoge[0]を返すべきで、
String getHogeName(); のように文字列を返すならば""という空文字列を返した方が良いと考えます。
何もしないオブジェクトも同様。


では、戻り値が単数で何も返さないことが正常値でもあるメソッドの場合はどうすべきか。

Hoge hoge = foo.getHoge();
if (hoge == null) {
    ...
}

とすべきか、
オブジェクト指向ではnullを返すべからず!」と主張される一例であるSaisseさんの

nullを返していけない理由としてだいたい以下の理由が挙げられます。

モデルのバグ -- モデルの世界ではnullというモノは存在しないのです。
実装依存 -- 実装依存よりもインターフェース依存を選択すべきです。
カプセル化の破壊 -- nullはカプセル化すべきです。
非明示的 -- nullを返すと複雑なコードになりやすいです。
まず押さえておかなければならないのは"return null;"というコードは実はオブジェクトの「状態」を表しているということです。例えばgetHoge()メソッドに"return null;"というコードがある場合はHogeという値を取得出来ない状態にあるということです。

が示すとおり、

Hoge hoge;
if (foo. isHogeGettable()) {
    hoge = hoge.getHoge();
} else {
    ...
}

とすべきなのか。


これはやっぱりコンテキストに依存するのでは、と(僕は)言わざるを得ません。
なぜかといえば、HashMapなどのような動きをするメソッドもあり得るからです。

public V get(Object key)
この識別情報ハッシュマップで指定されたキーにマップされている値を返します。マップがこのキーのマッピングを保持していない場合は null を返します。戻り値の null は、マップがキーのマッピングを保持していないことを示すとはかぎりません。つまり、マップが明示的にキーを null にマップすることもあります。containsKey メソッドを使うと、こうした 2 つの場合を見分けることができます。

HashMap (Java 2 Platform SE 5.0)

明示的にnullをマップしていることもある、*1
と文言が示すとおり、上記のisHogeGettable()はisHogeGettable(key)などになったとき、nullがマップされた状態を返す際にtrueを返さなければならないかもしれません。
とすると結果としてnullチェックを生ずる理由を生みます。(システム的にnullを絶対返さない、としていればいらないですが)


containsKeyは、マップされているかどうかを返しますが、それにnullがマップされているかどうかまで返しません、ということです。

それではnullを返したくなったらどうすればいいのか?ということで対処法を挙げておきます。

インターフェースで切り出す。 -- 上であげたbooleanを返すアクセッサを追加する方法。一番お手軽で確実。
例外を投げる。 -- エラー設計がきちんと行われていればこちらの方がいいかも知れません。
Nullオブジェクトを使う。 -- State/Strategyを使ってる場合に有効です。詳しくはその辺のパターン本で。
実際にやってみるといろいろ効果はあるのですが、NullPointerExceptionに出会わなくなるというのがけっこうおおきかったりします。それだけでも実践してみる価値はあるのでは?

インターフェイスで切り出す?
でも、isGettableNull(key)はちょっといやです。

では、nullオブジェクトを使う?
僕はC++のようにnullが定義されていない状態ならば

先端技術応用null

NULLについて

などを使う手はありだと思いますが、nullがあるのにnullオブジェクトを指定するのはどうも直感的ではなく気が引けます。


例外は、といえばnullが正常値であるのに例外を投げる、というのも何だかおかしな話に(僕には)思えます。


で、結論としてはnullを返すべきシーンではnullを返すべき、なのではないか。
と考えます。
そんなのないよ、という場合は(その開発チームは)返さない、というのがすべてかとも。

*1:Set,Listもnull許容です