例外設計③Throwableの扱い方・ErrorとException
例外設計③Throwableの扱い方・ErrorとException
ここ最近例外設計について2つの記事でまとめてきたが、
[DDD]例外設計の基本概念①
[DDD]Kotlinで実装する例外設計② - 型安全に例外を扱う
ThrowableってなぜGlobalExceptionHandlerで扱わないの?ということで
ちょっと基礎に立ち返ってまとめてみました。
Throwableって何者
Throwable は、Javaにおいて 「throw可能な異常」を表す最上位クラス で、
スタックトレースや原因例外などの共通機能を持つ例外処理の基盤クラスであり、
回復を想定しない致命的な異常は Error、
アプリケーションで扱う異常は Exception として表現される。
(Javaで 投げられる(throwされる)ものはすべて Throwable のサブクラス)
簡単にクラス階層表すと以下のようになる。
java.lang.Object
└── java.lang.Throwable:投げられるもの全ての基底クラス
├── java.lang.Error:システムレベルの重大なエラー
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ ├── VirtualMachineError
│ └── などなど
└── java.lang.Exception:アプリケーションレベルの例外
│ ├── RuntimeException: 非チェック例外
│ | ├── NullPointerException
│ | ├── IllegalArgumentException
│ | ├── IndexOutOfBoundsException
│ | └── など
└── └── IOException、SQLExceptionなど:チェック例外
ErrorとExceptionの違いは
| 観点 | Error | Exception |
|---|---|---|
| 発生原因 | JVM / 実行環境 | アプリケーションの処理 |
| 回復可能性 | ほぼ不可能 | 状況次第で可能 |
| catch対象 | 原則しない | する |
| 例 | OOM, StackOverflow | 入力不正, IO失敗 |
Errorとは何か
JVMレベルの致命的障害.
(アプリのロジックでどうこうする対象ではない)
ex. OutOfMemoryError, StackOverflowError, VirtualMachineError
なぜキャッチしないのか
- すでに プログラムが正常に動ける前提が壊れている
- キャッチしても「次に何をするか」が定義できない、そのまま落とす
Exceptionとは何か
アプリケーションが想定し、制御フローとして扱う異常.
- チェック例外: ex. IOException, SQLException
- 非チェック例外: ex. NullPointerException, IllegalArgumentException
なぜ扱うのか: 「起こりうる」前提で設計されている
ex. 入力不正,外部I/O失敗,業務ルール違反
なぜThrowableをGlobalExceptionHandlerで扱わないのか
1. Error まで握りつぶすから
OutOfMemoryError や StackOverflowError を “普通の500” にして返すと、
「死にかけてるのに生きてるフリ」をして障害を悪化させる。
2. フレームワークの制御フローまで壊す可能性
Spring内部は例外種別で挙動が変わる。Throwable で雑に捕まえると副作用が出る。
3. 観測性が落ちる
“何でもキャッチ” はログがノイズ化し、根本原因の特定が遅れる。
そのため、GlobalExceptionHandler では Throwable ではなく、
Exception(もしくは RuntimeException)を最終的なハンドリング対象とするのが基本となる。
Discussion
ThrowableとExceptionの違いがとても分かりやすく整理されていて勉強になりました!特にErrorまでキャッチしてしまうリスクについての解説が参考になりました。共有ありがとうございます。