【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. コードが複雑になる
  2. 不安になる
  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 をしないようにしよう。安易なデフォルト値はバグの見落としに繋がる。
  • 仕様上、必ず存在する値だとわかったら、迷わず getOption から取り出そう。大丈夫、もしエラーが出た時は、バグを発見できたと喜ぼう。

RDBのカラムを安易に nullable にするやつを俺は許せない。