新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。もう新人じゃないかも…

学習記録:ドワンゴ 新人向けScala研修テキスト

14. エラー処理

Scalaでのエラー処理は例外を使う方法と,Option/Either/Tryなどのデータ型を使う方法を状況に応じて使い分ける。
まずは扱う必要のあるエラーとエラー処理の性質について確認する。

エラーとは

ユーザからの入力

ユーザから受け取る不正な入力として,文字列が長すぎる,正しいフォーマットではないなどがある。
また,悪意のある攻撃者から攻撃を受けることもある。

外部サービスのエラー

プログラムが利用する外部サービスとの連携エラーも考えられる。
接続できない,回線の都合で接続が切れる,メール送信エラーなど。

内部エラー

内部要因でエラーが発生することも考えられる。
バグによるプログラムの終了,内部で利用するDBサーバが落ちる,メモリやディスク不足,処理時間の長さによるタイムアウトなど。

エラー処理で実現しなければならないこと

例外安全性

例外が発生してもシステムがダウンしたり,データ不整合などの問題が起きないことを例外安全という。
この概念はエラー処理全般に当てはまる。

強い例外安全性

さらに強い概念である「強い例外安全性」といい,これは例外が発生した場合すべての状態が例外発生前に戻らなければならないという制約を指す。
一般的にはこの制約を満たすのは難しいが,例えば課金処理でエラーになった場合は,確実にエラーを検出して処理を取り消す必要がある。
どのような処理に強い例外安全性が求められるか判断し,どう実現するか考える必要がある。

Javaにおけるエラー処理

Javaのエラー処理の方法を適用できることも多い。ここではJavaのエラー処理の注意点について復習する。

nullを返すことでエラーを表現する場合

Javaではnullでエラーを表現することがある。エラー値を他に用意する必要がないという点では便利だが,返り値のnullチェックを忘れるとぬるぽエラーになってしまう。

プリミティブ型以外の参照型はすべてnullになりうるので,メソッドがnullを返すかどうかはドキュメントに書いておかないとわからない(型で判断できない)。
また,nullをエラー値に使うと暗黙的なエラー状態をいたるところに持ち込むことになり,発見困難なバグを生む要因になる。ScalaではOptionというデータ構造によりこれを解決する。

例外を投げる場合

例外は今実行している処理を中断して大域的に実行を移動できる便利な機能だが,濫用すると処理の流れがわかりづらくなる。
例外はエラー状態にのみ利用し,メソッドが正常な値を返す場合には使用すべきではない。

チェック例外

Javaにはメソッドにthrowsを付けることで,メソッドを使う側に例外を処理することを強制するチェック例外という機能もある。
例外の発生を表現しコンパイラにチェックさせるという点で便利だが,使う側が適切に処理できない例外を上げられると無意味なエラー処理コードを書かざるを得なくなる。
チェック例外は利用者側がcatchして回復できる場合にのみ利用すべき。

例外翻訳

今までHTTPで取得したデータをMySQLに保存するようになった場合,HTTPExceptionが投げられていた箇所がSQLExceptionを投げるようになるといったことが考えられる。
このような低レベルの実装の変更で,catchする側で処理を変更するのを防ぐため,途中の層で例外をcatchして適切な例外で包んで投げ直すことを,例外翻訳と呼ぶ。
これも濫用すると例外の種類が増えて例外処理が煩雑になる可能性があるので注意が必要。

例外をドキュメントに書く

チェック例外でない例外はAPIから読み取ることができない。
更に,Scalaではチェック例外がないので,メソッドの型からはどんな例外を投げるかは判別できないので,APIドキュメントには発生しうる例外についても書いておくべき。

例外の問題点

Scalaでも例外は多く使われるが,便利な反面様々な問題もある。
ここで例外の問題点を把握する。

往々にして例外のcatch漏れが発生する。また,catch部分ではどこで発生した例外をcatchしているのか判別できない。

  • 非同期プログラミングでは使えない

送出されたらcatchされるまでコールスタックを遡っていくという性質上,別スレッドなどで実行される非同期プログラミングとは相容れない。

  • 型チェックできない

チェック例外を使わない限り,どんな例外が発生するのかメソッドからは判別できず,catchする側でも正しい例外をキャッチしているかは実行時にしかわからない。

Scalaではチェック例外の機能はなくなっている。上記の問題点のほか,以下のような問題点が理由としてあったと考えられる。

  • 高階関数でチェック例外を扱うのが難しい
  • ボイラープレートが増える
  • 例外翻訳を多用せざるを得ない

Scalaではチェック例外の代替として,エラーを表現するデータ型を使い,エラー処理を型安全にすることができる。