0
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Effective Java】抽象概念に適した例外をスローする

Posted at

Effective Javaの独自解釈です。
第3版の項目73について、自分なりにコード書いたりして解釈してみました。

概要

  • 下位レイヤーの例外はそのまま出力させるのではなく、上位レイヤーの概念に応じた例外に翻訳することで以下のメリットが得られる。
  • ログ調査しやすくなる
  • さらに上位のレイヤーでの例外ハンドリングがしやすくなる
  • 実装で例外発生を確実に防げるなら、翻訳は考えず例外発生そのものを防ぐ。

説明

以下の環境で実装したコードで説明する。

  • Java 11
  • Doma 2.19.2
  • Spring Boot 2.3.3
  • MySQL 5.7

DB挿入時における例外出力の例

DBにUSERテーブルがあるとし、レコードを新規追加することを考える。
USERテーブルには主キーとしてidカラムを持っている。

Java側の関連クラスは以下の通り。(importなど略)

Entity

public class User {

    private String id;

    private String name;
}

Dto

public class CreateUser {

    private String id;

    private String name;
}

Daoインタフェース

public interface UserDao {

    @Insert
    int insert(User user);
}

下位レイヤーの例外をそのまま出力させた場合

既にUSERテーブルに存在するidを持つcreateUserを引数に入れ、以下のサービス層のメソッドでinsertを試み例外を発生させる。

public User createUser(CreateUser createUser) {

    User user = new User();
    user.setId(createUser.getId());
    user.setName(createUser.getName());

    userDao.insert(user);

    return user;
}

このとき、標準エラーには以下が出力される。

〜略〜
nested exception is org.springframework.dao.DuplicateKeyException: [DOMA2004] 一意制約違反により更新処理が失敗しました。
SQLファイルパス=[null]。
ログ用SQL=[]。
詳しい原因は次のものです。{2}; nested exception is org.seasar.doma.jdbc.UniqueConstraintException: [DOMA2004] 一意制約違反により更新処理が失敗しました。
SQLファイルパス=[null]。
ログ用SQL=[]。
詳しい原因は次のものです。{2}] with root cause

java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'A01' for key 'PRIMARY'
〜略〜

Daoレイヤーから出力されたDuplicateKeyExceptionがそのままcreateUserメソッドを突き抜けてスローされている。

弊害としては以下の通り。

  • スタックトレースをたどればcreateuserメソッドで主キー重複例外が出てるから、USERのID重複エラーだなと一応わかるが、ひと目ではわかりにくい。
  • 上位レイヤーのコントローラ層で、Entityに応じたエラーハンドリング処理をしたい場合、DuplicateKeyExceptionが投げられると、どのEntityに対しての例外なのか判別するために、ややこしい文字列解析などする必要が出てくる。

上位レイヤーの例外に置き換えた場合

createUserメソッドの主な関心事はDBの制約ではなく、Userそのものである。ということでUserに着目した独自例外を作ってやり、それに翻訳する処理を施す。

独自例外

public class UserDuplicatedIdException extends Exception {

    public UserDuplicatedIdException(String message, Throwable cause) {
        super(message, cause);
    }
}

Primary KeyというDB寄りの言葉は使わず、あくまでUseridというUser寄りの言葉で例外クラスを作る。
createUserメソッドは以下のように、DuplicateKeyExceptionからUserDuplicatedIdExceptionへの翻訳処理を実装する。

public User createUser(CreateUser createUser) throws UserDuplicatedIdException {

    User user = new User();
    user.setId(createUser.getId());
    user.setName(createUser.getName());

    try {
        userDao.insert(user);
    } catch (DuplicateKeyException e) {
        throw new UserDuplicatedIdException("Can't create already existing id user.", e);
    }

    return user;
    }

先程の例と同じようにid重複のUSERテーブルレコードを挿入しようとすると、以下の標準エラー出力が出力される。

〜略〜
nested exception is com.demo.domain.exceptions.UserDuplicatedIdException: Can't create already existing id user.] with root cause

java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'A01' for key 'PRIMARY'
〜略〜

createUserからは、このレイヤーの関心事であるUserに関する例外UserDuplicatedIdExceptionが出力される。

利点は先程とは逆で以下の通り。

  • 例外クラスを見ただけでどのEntityで何が起こったかがひと目で分かる。
  • コントローラ層では、catchした例外クラスに応じて楽に分岐処理を実装できる。

また、障害やデバッグ時にログ調査をする際も、目的の例外クラスでgrepをかけられるので捗る。

例外翻訳は乱用しない

先述の例で、USERidNullで登録しようとすると、以下の標準エラーが出力される。

〜略〜
nested exception is org.springframework.dao.DataIntegrityViolationException: [DOMA2009] SQLの実行に失敗しました。
SQLファイルパス=[null]。
ログ用SQL=[]。
原因は次のものです。java.sql.SQLIntegrityConstraintViolationException: Column 'id' cannot be null。
根本原因は次のものです。java.sql.SQLIntegrityConstraintViolationException: Column 'id' cannot be null; SQL [insert into USER (id, name) values (?, ?)]; Column 'id' cannot be null; nested exception is java.sql.SQLIntegrityConstraintViolationException: Column 'id' cannot be null] with root cause

java.sql.SQLIntegrityConstraintViolationException: Column 'id' cannot be null
〜略〜

なるほど、この場合はDataIntegrityViolationExceptionを別の例外に翻訳すればいいんだなと思うかもしれないが、それはNGである。

insert時のDBの中身はクライアント側で制御できないが、insertしようとするEntityは制御可能である。
コントローラ層でリクエストのid指定がない場合はバリデーションで弾くなり、代替値を入れるなりすれば、この例外は100%発生しないので、翻訳処理も必要なくなる。

0
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?