最近、モバイルアプリの開発をするようになって、swiftとkotlinを勉強するようになった。
そんな中で初めて「Optional型」という概念に触れて「そんな便利な型が存在する言語があるのか」と思っていたのだが、よくよく調べてみたら、Java8にもOptional型があるというのを今更ながら知った。
以下はOptionalに関するJavaDoc。
https://docs.oracle.com/javase/jp/8/docs/api/java/util/Optional.html
Optional型とは
null
(言語によってはnil
)を格納できる変数の型のこと。
Javaの場合は、int
やboolean
などプリミティブ型はnull
を格納できない(コンパイルエラー)が、Integer
やString
など参照型の変数はnull
が格納されるかもしれないため、事前にnull
チェックをしないと、NullPointerException
が発生するかもしれない。
Optional型のメリット
プログラム初心者の自分が最初に感じた疑問。
参照型も
null
入るんだからOptional
型ってわざわざ定義する必要なくね?
たしかに「null
になるかもしれない型」という意味ではOptional型も参照型も関係ない。重要なのは、参照型の場合はnull
チェックしなければいけない処理で、それを怠ってしまいがちな点だろう。
null
チェックを怠った場合、NullPointerException
が発生するが、これは実行時例外に該当するため、最悪の場合は本番障害にならないと気づかないケースもありえる。
String str = getString();//これがnullを返した場合
//以下のif文を忘れてしまうとコンパイルは通るが、実行時にNullPointerExceptionに
//if(str == null){
// str = "";
//}
return str.length();
対して、Optional型を使った場合は、専用のメソッドを使わなければ値を取り出せないため、必ずnull
の場合を考慮したコーディングをしなければならない。
つまり、意図せずnull
が入り込んだ場合にどうするかを見逃さなくなる点が一番のメリットだと思う。
Optional<String> opt = Optional.ofNullable(getString());
String str = opt.orElse(""); //Optionalから値を取り出すには、nullを考慮したメソッド`orElse`を使わないと取り出せない。
return str.length();
でも本来は想定外のnullをなくすべき
なぜだろう。最近すっかり忘れてしまっていたのだが、本来は想定外の事態が発生しても大丈夫なようにしておく(null
チェックをしておく)ことが大切なわけではなく、想定外の事態を作り込まない(null
が返却される原因をなくす)ことの方が重要だ。
先ほどのコードで例えるならば、
String str = getString();//そもそもgetStringがnullを返すことが悪いのでは?
//以下の処理は記述しておくべきだったけど、getString()がnullを返すなんて仕様書に載ってないじゃん
//if(str == null){
// str = "";
//}
return str.length();
というケースのことだ。
null
を考慮すべき責任は誰(どのコード)にあるのかがはっきりしないまま、とりあえずnull
チェックしておけば安全だからと安直に考えていないだろうか。
経験上、先例のgetString
に該当するモジュールは、他ベンダーによって大昔に作られた前提モジュールであったり、すでに稼働済みの他システムでも流用されているため、請け負った案件の保守費用などの関係から「改修できないモジュール」扱いにされていたりすることが多く、「設計としてあるべき」という思想ではなく「現実的に考えてこうするしか無い」という事情に合わせてコーディングされることが圧倒的に多い。
今回記事をまとめようと思ったきっかけは、最近自分自身も「現実的に考えてこうするしか無い」という事情に甘んじて、とりあえずnull
チェックしておけば安全だからと安直に考えて実装するようになってしまったように感じたからだ。
本来は、意図的にnull
にしているのではなく、プログラマの不注意によりnull
が入り込む実装になったまま放置されているモジュールや、意図的にnull
にしたことが周知されずに、あるいは時代を経て「意図的にnull
にしたよ」という内容の引き継ぎが途絶えて「この場合にnull
になるなんてありえない」と判断してしまう行為自体が悪なのだ。
まとめ
Optional型はnull
の場合の考慮をプログラマに強いることができるが、本来null
をどう扱うべきなのかを設計する思想は忘れてはならない。(←自分への戒め)
たとえば、getString()
がnull
を返すかもしれないモジュールであるならば、getString()
の戻り値もOptionalにすべきである。
Optional<String> opt = getString();//Optional型を返すことを明示するべき
String str = opt.orElse("");
return str.length();
あるいは、getString()
がnull
を返さないことが保証されるべきであるならば、getString()
内部処理を修正して、呼び出し側はOptional型を使わなくてもいい。
String str = getString();//絶対nullではないことが保証されたなら
//以下のif文はデッドコードとなってしまうため、実装しないのが正しい
//if(str == null){
// str = "";
//}
return str.length();
swiftやkotlinなどOptionalが使える新しい言語でも、この思想は忘れてはいけないだろう。