エラー処理の温故知新¶
チェック¶
- [ ] 本文を確認した
- [ ] 概要を確認した
- [ ] タグを確認した
- [ ]
inbox/直下へ移行した
概要¶
エラー処理を C 言語の戻り値、例外、Java の検査例外、Go の値としてのエラー、Rust などの Result 型という流れで振り返る資料。 エラーは「期待と現実のギャップ」であり、プログラムの前提が崩れた瞬間として説明されている。 各方式には、明示性、可読性、強制力、伝播コスト、制御フローの追いやすさというトレードオフがある。 結論は、銀の弾丸はないため、言語ごとの思想を理解して使い分けること。
解説¶
この資料は、Go の if err != nil が古くさいのか、例外が本当に優れているのか、Result 型は何を改善したのかを比較するための材料になる。
エラー処理は構文の好みではなく、どの失敗をドメイン上の通常事象として扱い、どの失敗を回復不能として扱うかの設計問題。
バックエンドでは、入力不正、認可失敗、外部 API のタイムアウト、DB 制約違反のような「予期される失敗」と、OOM や invariant 破壊のような「通常ロジックで回復しない失敗」を分けるのが重要。 Go のエラー値は前者を明示的に扱うのに向いているが、無視できてしまうため、レビューや lint、設計規約が必要になる。
本文¶
本文は Speaker Deck の transcript から取得できた内容をもとに、日本語で再構成したもの。
エラーとは何か¶
資料では、エラーをプログラムの期待と現実のギャップとして説明している。
- 存在するはずのファイルがない
- 正の整数のはずの値が負である
- 応答するはずのサーバーがタイムアウトする
- 型が食い違う
- null pointer が発生する
プログラムが置いた前提は必ずどこかで崩れる。 そのため、プログラムを書くことはエラーハンドリングを書くことでもある。
ドメインエラーと非ドメインエラー¶
資料では、エラーを大きく 2 つに分けている。
ドメインエラーは、入力不正、前提条件違反、認証・認可失敗など、業務ロジックやアプリケーション上で予期される失敗。 非ドメインエラーは、OOM、ネットワーク障害、null pointer 参照など、通常の制御フローで扱いにくい失敗。
この区別をしないと、すべてを例外にする、すべてを戻り値にする、という粗い設計になりやすい。
C 言語の時代¶
C 言語と Unix 的な世界では、エラーは値だった。 関数やプログラムは成功・失敗を数値で表し、0 なら成功、0 以外なら異常という形をとる。
この方式はシンプルだが、戻り値をチェックし忘れるリスクがある。 また、正常系と異常系が同じ制御フローに混ざるため、可読性が落ちることもある。
例外の時代¶
C++、Java、Python、Ruby などでは例外が一般的になった。 例外は正常系と異常系の流れを分けやすく、大域脱出によってチェック漏れを減らせる。
一方で、関数定義を見ただけでは何が投げられるかわかりにくい。 どこで throw され、どこで catch されるかが離れやすく、制御フローが暗黙になる。 意図せず握りつぶされる、誰も捕捉しない、といったリスクもある。
Java の検査例外¶
Java の検査例外は、関数が投げうる例外をシグネチャに表現し、呼び出し側に catch するか throws で伝播するかを強制する。 処理漏れをコンパイル時に防げる一方で、伝播コストが高い。
例外の種類を増やすと呼び出し側全員に影響し、API 変更に弱くなる。 高階関数や関数型記法との相性も悪くなる。 失敗可能性を明示することと、その場で回復できることは別問題だった、という整理。
Go のエラー処理¶
Go は、あえてエラーを値として返す方式を選んだ。
panic はあるが、通常の失敗は例外ではなく値として扱う。
ファイルがない、入力値がおかしい、といった事象は特別な制御構造で扱うほど例外的ではない。 その場で処理するか、呼び出し元に返す。 暗黙の大域脱出を避け、制御フローを明示するのが Go の方向性。
ただし、Go のエラー値は無視できる。 そのため、型システムだけでは処理漏れを防げず、開発者の規律が必要になる。
Result 型の時代¶
Rust、Haskell、Scala、Swift、Kotlin などでは、失敗可能性を型で表す方向が強い。 成功なら値、失敗ならエラーという構造を型として表現する。
Result 型では、中身を取り出すために成功・失敗を扱う必要がある。 そのため、うっかり無視しにくい。 Java の検査例外のように各階層で宣言を強制するのではなく、どの階層で処理するかは開発者が選べる。
要点¶
- エラー処理は、失敗をどう制御フローと型に表すかの設計問題。
- C / Go の値方式は明示的だが、無視できてしまう。
- 例外は正常系を読みやすくできるが、暗黙の大域脱出で追跡が難しくなる。
- 検査例外は強制力があるが、伝播コストと API 変更の重さがある。
- Result 型は失敗可能性を型に閉じ込め、処理漏れを減らす方向の設計。