先日の達人プログラマー輪読会で社内でt_wadaさんが語ってくれた話をまとめました。
例外以前
例外以前はreturnで表現してた。
int result = hoge();
if (result < 0) {
// result の値が負の値だったら例外として扱う
}
returnだと呼び出し側で評価しなくても例外のキャッチをサボれてしまい、サボりやすくなる。
// 戻り値を検証しないだけで例外をサボれる
hoge();
try-catch
この問題に対処するために例外をtry-catchで受け取るパターンが生まれてくる。
try {
hoge();
} catch (e) {
// 例外処理
}
ただ今度はこれだとthrow例外はtry句内ではどこでもcatch節に飛べてしまいlong jumpになる。
try {
//
for (...) {
hoge(); // throw error
}
//
} catch (e) {
// forループの状態を無視してここに来る
}
またこの他にもtry-catch特有の問題としては、
- リソースの解放がやりにくくなる(try-with-resourceが必要になる)。
- 非同期呼び出しでスレッドを跨いでしまうと例外をうまく補足できない。
という問題がある。このため、
例外はマルチコア時代にそぐっていないという話、非常に面白かった。
— Yosuke FURUKAWA (@yosuke_furukawa) April 14, 2017
という発言に繋がる。
最近の言語
これを最近のモダンな言語では、try-catchを使っていないことが多い。
例えば Golangでは結局タプルをreturnで表現した上で値が使われていないとコンパイラが怒る。また、非同期呼び出しでは例外はchannelを使って渡すことも多い。
この他にも戻り値を表現する時にResult(Rust)やPromise(JavaScript)などの値を抽象化したクラスで例外を表現するというやり方が出てきている。
JavaScriptに関して言えば過去にはイベントとしてエラーを表現するStreamもあり、さらにES2017ではasync-awaitが入って、非同期もキャッチできるtry-catchが増えている、このように同期と非同期の垣根は少なくなってきている。やり方は定まりきっていないが、JavaScriptはTIMTOWDIな言語なので一旦これで良いとしよう。
達人プログラマー輪読会の話の中で語っていた重要な点
重要なのは言語やライブラリ、アプリケーションによって例外とかエラー処理をハンドリングする方法も、また何を例外として捉えるかの考え方が違うので、あまり一様な扱いはできない、逆に言うと別文化圏から来るとこの辺の書き方は慣例に習っていく必要がある。