執筆者: オキリョウ
最終更新: 2022/05/06
こんばんは、オキリョウです
Kotlin 1.7.0がついにきましたね!
https://blog.jetbrains.com/kotlin/2022/05/kotlin-1-7-0-beta/
みなさん期待を膨らませていることだと思います。
ところで、このバージョンの目玉として取り上げられているものの中に、max
関数及びmin
関数があります。
名前を見てわかる通り、これはリストの中の最大、最小の値を取ってくる関数になります。
こんな原始的な機能すらKotlinにはなかった・・・というわけではありません。
代わりにmaxOrNull
、minOrNull
という関数が存在します。
では何が違うのかというと、最大値及び最小値が存在しないときの挙動です。
となっています。
最近では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
は何もないことを表す番地へのポインタです。
先程の実行例を見てみましょう。
Integer value = 5;
これは、5
という値がとあるアドレスに格納されていて、そのアドレスの番地をvalue
が保存している・・・という構造になっています。
ではnull
の場合はどうでしょう?
Integer value = null;
これは、何も意味がないというアドレス(通常は0アドレスとからしい)の番地をvalue
が保存しています。
ここから、value
自身は両者とも同じようにポインタを指していることがわかります。
問題は、そのポインタの先に値がある場合とない場合があるということです。
そのせいで、実行時ではないとエラーがわからないのです。
説明がややこしくてわからん!という人は以下のように考えましょう。
Integer型とは、暗黙的にInteger or null型になっていた
これらはもちろん、他の型でも当てはまります。
例外として、プリミティブ型と呼ばれるポインタを使わない型にはnull
は使えません。
ここまで色々話してきましたが、結局null
の何が恐ろしいのでしょうか?
まとめると以下の通りです。
これらのことから、エンジニア界隈ではnull
を恐れる人が多発する事になりました。
このことから、null
を開発した張本人も後悔の念を表明しています。
https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/
ここまでで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
型について勉強してみてください。これを使えば完全に例外のない世界も実現可能です。
この人が書いた記事