トップ画像
Nullはなぜ危険なのか

執筆者: オキリョウ

最終更新: 5/6/2022

こんばんは、オキリョウです

Kotlin 1.7.0がついにきましたね!
https://blog.jetbrains.com/kotlin/2022/05/kotlin-1-7-0-beta/
みなさん期待を膨らませていることだと思います。

ところで、このバージョンの目玉として取り上げられているものの中に、max関数及びmin関数があります。
名前を見てわかる通り、これはリストの中の最大、最小の値を取ってくる関数になります。

こんな原始的な機能すらKotlinにはなかった・・・というわけではありません。
代わりにmaxOrNullminOrNullという関数が存在します。

では何が違うのかというと、最大値及び最小値が存在しないときの挙動です。

  • 従来 -> nullを返す
  • 今回 -> エラーを投げる

となっています。

最近ではNullは敬遠されがちです。その流れを汲み取ってこの関数を実装したのでしょう。
ものすごく良さげな機能に見えますね。
でも、実は私、あまり良い気がしていません。
とりあえずNullは危険だ!という理由でより危険なこの関数を使う人がいるかも?と思っているからです。

そこで、なぜNullが危険と謳われているのかを解説していきたいと思います。

Nullの危険性

Nullの危険性に触れてみる

まず、以下のコードを見てみましょう。こちらはJavaのコードになっています。

public static void main(String[] args) {
    Integer value = 5;
    System.out.println(value * value);
}

至って普通のコードですね。出力結果は以下のようになります。

25


それではこのコードを少し編集してみましょう。

public static void main(String[] args) {
    Integer value = null;
    System.out.println(value * value);
}

value変数に入れる値をnullにしてみました。この場合どのような挙動をすると思いますか?
そもそも数値型にnullなんか入らなさそうだからコンパイルすら通らなさそうです。

実はこれ、コンパイルが通るんです。
ただ、実行しようとするとエラーを吐いて止まります。

Exception in thread "main" java.lang.NullPointerException


Nullの正体

なぜ上記のような挙動をするのでしょうか?
それはnullが何かを理解すればすぐにわかります。

nullは何もないことを表す番地へのポインタです。

先程の実行例を見てみましょう。

Integer value = 5;

これは、5という値がとあるアドレスに格納されていて、そのアドレスの番地をvalueが保存している・・・という構造になっています。

ではnullの場合はどうでしょう?

Integer value = null;

これは、何も意味がないというアドレス(通常は0アドレスとからしい)の番地をvalueが保存しています。

ここから、value自身は両者とも同じようにポインタを指していることがわかります。
問題は、そのポインタの先に値がある場合とない場合があるということです。
そのせいで、実行時ではないとエラーがわからないのです。

説明がややこしくてわからん!という人は以下のように考えましょう。
Integer型とは、暗黙的にInteger or null型になっていた
これらはもちろん、他の型でも当てはまります。
例外として、プリミティブ型と呼ばれるポインタを使わない型にはnullは使えません。

何が恐ろしいか

ここまで色々話してきましたが、結局nullの何が恐ろしいのでしょうか?
まとめると以下の通りです。

  1. 実行するまでエラーが出ないため、本番環境で爆発する可能性がある
  2. nullを参照した場所でエラーが起きるため、どこでnullが代入されたか(すなわち原因)を特定するのは困難
  3. nullを使わないように制御する方法がないため、人力でnullチェックをする必要がある(人力なので忘れがち)

これらのことから、エンジニア界隈ではnullを恐れる人が多発する事になりました。

このことから、nullを開発した張本人も後悔の念を表明しています。
https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/

Nullの現在

ここまででnullの恐ろしさを十分に理解できたのではないでしょうか?

しかし、ここまで恐ろしいと言われているnullをそのまま放置する人類ではありません。
比較的最近作成された言語では十分な対策が取られています。
有名な言語としてKotlinが挙げられます。
ではどのような対策をしているのでしょうか?

やっていることは単純です。null非許容型を用意したんです。

実際のコードを見てみましょう

val value: Int = null  // NG

このように、普通の型にはnullが入らないようにしました

nullを使いたい場合は、下のように?をつけることで許容型にする必要があります

val value: Int? = null // OK

また、この?を外さない限り、Int型のメソッドは呼び出せません。
外すにはnullが変数の中に入っていないか(いわゆるnullチェック)をする必要があります。
こうすることで強制的にnullチェックをさせるようにしました。

これで Int = Int or nullみたいなことは無くなったわけです。
もちろん、nullチェックがしやすい構文も用意されており、nullとの付き合いが断然楽になりました。

まとめ

かつて、nullは非常に恐ろしいものでした。
最も簡単にエラーを引き起こしてしまいますし、かつ原因がわかりづらい。
しかし、現在ではコードによる強制チェックができるようになったため、このようなエラーは随分と減りました。
むしろ、確実にチェックしなくてはならない存在となったのです。
チェックしないとそもそも関数呼び出せないですからね。

そのため、現在のコード上でのnullは結構使いやすい存在になったと個人的には思っています。
かといって乱用するのもどうかとは思いますが・・・
昔よりはnullを使うデメリットがガッツリ下がったと思ってください。

もちろん、Null非許容型が用意されていない言語の場合、nullは非常に危険です。
例えばJava, PHP等の古い言語、またモダン言語でも昔のDart等が挙げられます。
できる限り使わないようにするか、Option型の導入を検討しましょう。

それでは、お疲れ様でした!

補足

冒頭で述べた「nullより例外の方が危険だ」という事について補足しておきます。

例外の何が恐ろしいのかというと、コンパイラによってチェックされない事です。
どこからでも好きに投げれて、try-catchを使ってチェックをしなくてもコードに怒られない・・・
そうですよね?

ちょっと怖くないですか?
先程のnullで説明したことと同じような課題が出てきています。
このことから「近代のgoto文だ」と批判する人すらいるほどです。

私もこれには同感です。そこらでポンポンエラーを投げるくらいならnullで表現する方が安全だと思います。
大概のモダン言語上のnullであれば確実にチェックをする必要がありますので想像以上に安心して動かせます。
値が存在しなかったからnullを返すわけですし、結構自然な気もしますしね。

いや、nullじゃ表現力が足りない。型の堅牢さも足りていないから例外は必須だ!という人。良い着眼点です。
そんな人はぜひResult型について勉強してみてください。これを使えば完全に例外のない世界も実現可能です。

取得に失敗しました

2020年度 入部

Twitter GitHub