プログラミングマガジン

プログラミングを中心にIT技術をできるだけわかりやすくまとめます。

  • ホーム
  • Java
  • 【Java】汎用的な例外処理の対応方法、リトライの考え方など
 
 
     
  • サーバー言語  
    • Python
    • Ruby
    • PHP
    • SQL
  •  
  • インフラ  
       
    • AWS
    •  
    • 基本
    • Git
  • Web
       
    • Web開発
    • JavaScript
    • Vue.js
    • React
  •  
  • 設計  
       
    • 実装設計
    • DB設計
  • 問い合わせ
  

【Java】汎用的な例外処理の対応方法、リトライの考え方など

09.25

  • miyabisan2
  • コメントを書く

この記事は3分で読めます

歴史的背景

構造化プログラミング(C言語)の時代の例外処理

サブルーチン内でエラーが発生した場合はエラーコードを返却する形で実装するのが常套手段でした。

1の時はデッドロック、2の時は通信障害、3の時はシステムエラー

この方法の問題点

エラーコードの判定処理をアプリで確実に行う必要がある点

もし、判定処理を間違えたりすると障害発生時に原因特定が難しくなる。また、エラーコードの値を追加・削除する場合はプログラマが関係するサブルーチンを全て調べて書き換える必要が発生する。

エラーコードを判定する同じようなロジックがサブルーチン間で連鎖する点

呼び出す側のサブルーチンではエラーコードの判定処理を常に書かなくてはいけないので、プログラムが非常に冗長になります。

オブジェクト指向時代の例外処理

特別なエラーを返す可能性があることをメソッドで宣言します。

最上位の呼び出しもとのメソッドだけで、例外処理を下記、それより下位層のメソッドでは例外が発生する可能性があることだけを宣言すればよくなります。

重複したエラー処理を書かなくて良くなり、かつ必要な箇所でエラー処理を書き忘れた場合はコンパイラや実行環境が教えてくれます。

Javaで言えば、throw、throwsってやつですね。ただ、現実としてWebアプリとかを作る場合はフレームワークが例外処理機構を別途用意しているケースが多い(Spring MCVなど)のでそちらに倣う形になります。

例外を検知する側

RuntimeExceptionにラップして返す。

検査例外を非検査例外(RuntimeException)にラップして返します。とりあえずこれだけ覚えておけば最低限無難なコードになります。(e.printStackTrace();などで最低限出力してあるコードよりは)

1
2
3
4
5
6
7
try {
    ファイル処理など
    // 正常系
    ...
} catch (IOException e) {
    throw new RuntimeException(e);
}

なお、より適切な非検査例外にラップして返せるとなお良い。上の例で言えば、「UncheckedIOException」にラップして送出するのが適切です。

  • IllegalArgumentException(与えられたパラメータが想定外の場合)
  • IllegalStateException(呼び出された側の内部状態が想定外の場合)

アンチパターン

booleanで返す。

1
2
3
4
5
6
7
  try {
    処理
    return true;
  } catch (IOException e) {
    log.error("xxxx", e);
    return false;
  }

メリット

簡単に実装できる。

デメリット

呼び出し元に原因が通達されない。

自前でロギングしてから再送出する。

1
2
3
4
5
6
try {
  ファイル処理
} catch (IOException e) {
    log.warn("xxxx " + e, e); // 自前でログを出力するのはNG
    throw new UncheckedIOException(e);
}

これをすると、自前のログが出力された後、大域の例外ハンドラで再度同じようなログが出力されてしまう。これをすると運用時に二箇所別の原因でエラーが出ているものだと勘違いしてしまう。

非検査例外にラップし忘れる。

1
2
3
4
5
try {
  ファイル処理
} catch (IOException e) {
    throw new UncheckedIOException("オリジナルメッセージ");
}

これをすると、元の例外の原因の情報が損なわれてしまう。必ずエラーオブジェクトを非検査例外にラップすること。

try-with-resources構文を使う。(Java7以降)

1
2
3
4
5
try (ファイル処理) {
  ファイル処理
} catch (IOException e) {
    throw new UncheckedIOException(e);
}

finallyでファイルの後処理を記述すると、その中でもtry-catchをしなければならず冗長な記述になってしまう。Javaの環境が新しめであれば、「try-with-resources」を使った方が良いです。

Optionalで包む

下位層が例外を送出することになることが設計上適切とは言い難い場合に例外を補足してOptionalという戻り値を返す場合があります。

1
2
3
4
5
6
7
8
public Optional<型> メソッド() {
    try {
        処理
        return Optional.of(型);
    } catch (Exception e) {
        return Optional.empty();
    }
}

返り値のクラス(xxxResult)を実装する。

「成功した場合の戻り値となる値」と「失敗した場合の情報」を一つのクラスにして返す方法。かなり実装コストは高い。

Either型で返す。

関数型に近い概念。知識がある現場なら使っても良い。

リトライする。

外部API呼び出しとかのような処理で設計する場合もある。ただ、自前で実装することが難しいので外部ライブラリとかを使った方が良いケースが多い。

例外を受信する側

基本的な指針としては個々に記述することなく大域で例外クラスを処理するようにすると良いです。

アプリの最上位層(mainメソッドなど)でtry-catchを書く

シンプルでわかりやすいです。ただ、現実はあまり使われるケースは少ないです。

mainメソッドからスクラッチで開発するケースは少ないですし、Webアプリにしてもフレームワークにもっと良い大体手段があるためです。

Webフレームワークの例外処理機構を利用する。

普通のWebフレームワークなら例外クラスに応じた例外ハンドラを実行する機構を持ってます。Spring MVCなら例外ハンドラクラスや、例外メソッドに対してアノテーションで例外クラスを指定します。

DIコンテナのInterceptor機構を利用する。

DIコンテナの管理下にあるクラスの任意のメソッド呼び出しに対して前後処理を組み込めます。

アンチパターン

Exception Manager

分散型が主流の昨今だと実装は厳しい。リトライやロールバック実装が難しかったり、個別ケースごとに似たようなManagerがたくさんできる。

Exception Handler

これもAPIでいろんなサービスと交信し合うタイプのアーキテクチャだと厳しい。

リトライさせるか

リトライのユースケース

バッチ処理などでどうしても失敗できない処理の場合

バッチ処理はオンライン処理に比べたら同時処理数は一定のため過負荷などにはなりにくいです。

リトライの注意点

回数の多いリトライは過負荷を生む原因になるのでやったとしても数回程度にしましょう。

スポンサーリンク
  • 2021 09.25
  • miyabisan2
  • コメントを書く
  • Java
  • Tweets Twitter
  • このエントリーをはてなブックマークに追加
  • LINEで送る

関連記事

  1. 2018 05.13

    【GoFのデザインパターン】「Facade(ファザード)」ってどんなパターン?、設計方針なども。

  2. 2018 04.21

    【JSP】JSTLを使うには?(カスタムタグの基本や種類についても)

  3. 2018 04.15

    【サーブレット】画面遷移その1(ディスパッチ:リクエストの転送)

  4. 2018 05.12

    【GoFのデザインパターン】「Abstract Factory」ってどんなパターン?

  5. 2018 05.20

    【Eclipse】検索機能の種類

  6. 2018 05.06

    【Spring Framework】DI(依存性の注入)を実装してみる。(設定ファイルから)

  • コメント ( 0 )
  • トラックバック ( 0 )
  1. この記事へのコメントはありません。

  1. この記事へのトラックバックはありません。

返信をキャンセルする。

【DDD】フロントエンド に「クリーンアーキテクチャ」…

【データベース】テーブル設計:履歴設計の注意点、その対…

RETURN TOP

著者プロフィール

エンジニア歴10年で過去に業務系、Webデザイン、インフラ系なども経験あります。現在はWeb系でフロントエンド開発中心です。

詳細なプロフィールはこちら

スポンサーリンク

カテゴリー

  • Android
  • AngularJS
  • API
  • AWS
  • C++
  • CSS
  • C言語
  • DDD
  • DevOps
  • Django
  • Docker
  • Figma
  • Git
  • GitLab
  • GraphQL
  • gRPC
  • Hasura
  • Java
  • JavaScript
  • Kubernetes
  • Laravel
  • linux
  • MySQL
  • Next.js
  • nginx
  • Node.js
  • NoSQL
  • Nuxt.js
  • Oracle
  • PHP
  • Python
  • React
  • Redux
  • Rspec
  • Ruby
  • Ruby on Rails
  • Sass
  • Spring Framework
  • SQL
  • TypeScript
  • Unity
  • Vue.js
  • Webサービス開発
  • Webデザイン
  • Web技術
  • インフラ
  • オブジェクト指向
  • システム開発
  • セキュリティ
  • その他
  • データベース
  • デザインパターン
  • テスト
  • ネットワーク
  • プログラミング全般
  • マイクロサービス
  • マイクロソフト系技術
  • マルチメディア
  • リファクタリング
  • 副業
  • 未分類
  • 業務知識
  • 生成AI
  • 設計
  • 関数型言語
RETURN TOP

Copyright ©  プログラミングマガジン | プライバシーポリシー