【Scala】Option 利用指針
Scalaでよく使うものに Option
型があると思います。Option
型は値が存在するかもしれないことを示す型です。Javaで値がないを表現するためには null
を使うのが一般的でした。Java 8からは Optional
型 ができたようですが、古いJavaのプロダクトでは null
が使われていたと思います。
この、Option
ですが、非常に便利で無闇に多用されているのを見かけます。例えば、
val users: Seq[User] = Seq(User(1, "司馬"), User(2, "光井"), User(3, "北山")) val maybeUser: Option[User] = users.find(_.id == 1) // 仕様上、絶対存在するはず
このような形です。重要なのは「仕様上、絶対存在するはず」の部分です。仕様上存在するはずですが、find
メソッドは Option
で返すので、Option
のまま後続の処理を続けようとしているのを見かけます。
maybeUser match { case Some(user) => // 何らかの処理 case None => // 仕様上絶対に起こらないけど不安だから一応書いているハンドリング }
だったり、雰囲気で
val user = users.find(_.id == 1).getOrElse(User(0, ""))
のように謎の値がデフォルト値として採用されていたりします。私も新卒で入社して、Scala書きはじめた頃はこのような書き方をよくしてしまっていました。しかし、この書き方に大きな問題が3つ存在します。
3つの問題
- コードが複雑になる
- 不安になる
- バグに気づけなくなる
1 コードが複雑になる
これは単純です。Option
の値を参照するクライアントは Some
or None
でハンドリングをしなければなりません。仕様上必ず存在するはずの値をいちいちハンドリングするのは無駄でしかありませんし、コードの可読性を下げるだけです。
2 不安になる
頭の中では、絶対に存在すると分かっていても、値が Option
のままだと不安になります。この値は参照しようとしている今この瞬間本当に存在しているのだろうか。ないことはないのだろうかと不安になります。不安は脳のリソースを奪い、生産性を下げます。
3 バグに気づかなくなる
これが最大の問題です。最初から言っている通り、ここで議論しているのは「仕様上必ず存在するはず」の値です。仕様上必ず存在するはずの値が存在しない時、それはバグなので、バグとして気付けなければなりません。つまり、getOrElse
やパターンマッチでハンドリングしてしまうと、バグを握りつぶすことになってしまいます。非検査例外を、キャッチして握りつぶしてしまっている感じですね。
どうすべきか
単純です。最初の段階で get
してしまいましょう。
val user: User = users.find(_.id == 1).get
これで NoSuchElementException
が投げられたら、それはバグです。`users に問題があったことになります。
どういう時に見かけるのか
例に出した、配列の中に仕様上必ず存在しなければならない時もありますが、結構困るのは、RDBのテーブル定義が、値は必ず存在するが、テーブル定義は null
になっている(not null
になっていない)時です。テーブル定義を変えてしまうのがベストですが、DBスキーマ変更のコミュニケーションコストが高いプロジェクトの場合ですと、気軽に変更できなかったりもします。そういう時は、仕様をよく確認し、スキーマ上 null
だけど実はnull
にはならない値の時は迷わず get
を使います。
まとめ
- そもそも
Option
を気軽に利用しないようにしよう。Option
を使うべき時は、仕様上Optional な時だけだ。 - 安易に
getOrElse
をしないようにしよう。安易なデフォルト値はバグの見落としに繋がる。 - 仕様上、必ず存在する値だとわかったら、迷わず
get
でOption
から取り出そう。大丈夫、もしエラーが出た時は、バグを発見できたと喜ぼう。
RDBのカラムを安易に nullable
にするやつを俺は許せない。