プログラミング初心者がアーキテクトっぽく語る

見苦しい記事も多数あるとは思いますが訂正しつつブログと共に成長していければと思います

例外処理の設計

例外処理の構造について調べる機会があり、以下の3点について考えてみました。

  1. 例外が発生した場合の処理の仕方
  2. エラーを検知したときの例外の発生させ方
  3. 例外のcatchの仕方
  4. 階層をまたぐ例外

1. 例外が発生した場合の処理の仕方

呼び出したメソッドが例外を発生させた場合、どうすればよいか考えます。

1.1 共通

  • まずはどのように元の状態に戻せるか考える。
    • 「元の状態」= メソッドを呼び出す前の状態。
  • 次のアクションはその例外が回復可能か、回復不能かで異なる。
  • 対処がわからないときは上に丸投げ(throws)が最低ライン

1.2 回復可能な場合

  • catchして回復を試みる
    • 再接続、Fallback、切り替えなど

1.3 回復不可能な場合

  • 業務エラーか技術的なエラーかで対応が異なる

業務エラー

  • 業務ロジックの代替パスの一部
  • catchして代替パスに切り替える
    • エラー画面表示、再入力画面表示、再ログイン画面表示など

技術的なエラー

  • 上位に伝搬させて上位層に適切にプログラムを終了させる
  • catchした上位層はログ、管理者通知、ユーザ通知、ロールバック、リソース開放などを実施する
    • 通常、これらの処理は上位層で共通化されている
    • 下位の業務ロジック内で個別に実装しない方が見通しがよくなる
  • 検査例外、非検査例外の両方がありえる

1.4 注意 

  • 無視しない
  • 例外にはその場で処理すべきものと上位が処理すべきものがあることを理解する
    • 上位に伝搬すべき例外を止めない
    • その場で処理すべき例外のcatch漏れを防ぐ
  • catch Exceptionしない
    • なにが発生するか読み取れない(可読性低下)
    • 上位に伝搬すべき例外(特に非検査例外)まで止まってしまい、例外設計が崩れる(想定外動作)
  • throws Exceptionしない
    • その場で処理すべき別の例外をcatch漏れしていてもコンパイラが気づけない(想定外動作)
  • 上位層への伝搬を全て止める親クラスを書かない
    • 実装ミスがあっても決して死なない不死身の子クラスが誕生する
    • 問題発生の検知や切り分けが難しくなる(運用性低下)
  • 上位へ伝搬させる例外をe.printStackTraceしない
    • 伝搬先の上位での出力処理と重複して混乱する(運用性低下)
    • 入門書などでよく見かけるので初心者がやりがちなミス

2. エラーを検知したときの例外の発生させ方

エラーを検知したとき例外を発生させたいときがあります。 どのように発生させればよいか考えます。

2.1 呼び出し側が防げるエラーの場合

  • プログラミングエラーとも呼ばれる
  • 呼び出し側はtry/cachによる事後対処ではなく、事前のチェックで防ぐべき
  • よって非検査例外をスローする

2.2 呼び出し側が防げないエラーの場合

  • 呼び出し側が万全を期しても発生を防げない=呼び出し側でtry/catchによる事後対処が必要
  • よって検査例外をスローする
  • しかし検査例外について賛否があり、案件ごとに関係者で意識をあわせる
    • (批判)新しい検査例外は伝搬経路上のメソッドのthrowsやcatchに追加される必要がある=Open Closed Principle違反
    • (利点)try/catchが必要な例外を利用者に確実に伝えることができる

2.3 発生させる例外クラス

  • Java標準の例外クラスを発生させるか、独自のアプリケーション例外を作成して発生させるか好みが分かれる
  • アプリケーション例外は表現力、抽象力、バラエティーに勝るが数や種類が制御できなくなるリスクがある
  • 案件ごとに関係者でルールを決める

3. 例外のcatchの仕方

例外とはエラーが発生したことを上位層へ伝搬させる仕組みなので、上位層のどこでcatchするかは重要なポイントです。

  • 前述の通り回復可能な例外は発生元の近くでcatch&回復する
  • 回復不能な例外のcatchは処理を呼び出す「根っこ」のクラスにのみに実装することを基本とする
    • Baseクラス=Routing担当と業務ロジックの間にある業務ロジックの親クラス
    • ログ出力などもBaseクラスにまとめることができる
    • 最近ではFilterやAOPなどFrameworkの様々な機能を使える
    • これにより末端クラス側をすっきりさせることができる

4. 階層をまたぐ例外

境界における例外は、抽象化することが重要です。

  • 階層の境界で投げる検査例外は抽象化されたものにする
    • パッケージ固有の例外だとパッケージを入れ替えるたびに対向側はcatchやthrowsを書き換えなくてはいけない=Open Closed Principle違反
  • 下位層のパッケージが投げる検査例外は回復されることを期待しない
    • 上位層が例外を回復できるということは上位層が下位層の詳細に依存しているとうこと
    • 回復できるものは下位層内で回復するよう努力する必要がある
    • これには例外もある