Tagbangers Blog

例外チェーンとその重要性について

この記事はBaeldungのChained Exceptions in Javaという記事を参考にし、一部翻訳したものです。

Exceptionとは?

Exception(以下、例外)とは一言でいうと、プログラムの正常な流れを妨害するイベントのことです。

Chained Exceptionとは?

Chained Exception(以下、例外チェーン)は、一つの例外が他の例外によって引き起こされているという状況を確認しやすくしてくれるものです。

例えば、0で割り算をしようとした関数からArithmeticExceptionが投げられたとします。しかし、実際の原因が別にあり、そもそも0が除数として入力されたこと、つまり入力エラーだったとします。この場合、関数は呼ばれた相手に対してArithmeticExceptionを投げますが、根本の原因である入力エラーについてはなにも伝えません。このような状況で例外をチェーンさせることで、エラーの根本的な原因をトレースできるようになります。

例外のもみ消し

上記の割り算関数の例は、例外のもみ消しといい、バッドプラクティスの一つです。

以下のサンプルコードをご覧ください。TeamLeadUpsetExceptionがキャッチされていますが、その例外eは出力されるだけで、次に投げられるNoLeaveGrantedExceptionに渡されていません。これは例外のもみ消しのよい(悪い?)例です。

public class MainClass {
 
    public void main(String[] args) throws Exception {
        getLeave();
    }
 
    void getLeave() throws NoLeaveGrantedException {
        try {
            howIsTeamLead();
        catch (TeamLeadUpsetException e) {
            e.printStackTrace();
            throw new NoLeaveGrantedException("Leave not sanctioned.");
        }
    }
 
    void howIsTeamLead() throws TeamLeadUpsetException {
        throw new TeamLeadUpsetException("Team Lead Upset");
    }
}

これに対するログは以下のようになります:

com.baeldung.chainedexception.exceptions.TeamLeadUpsetException: 
  Team lead Upset
    at com.baeldung.chainedexception.exceptions.MainClass
      .howIsTeamLead(MainClass.java:46)
    at com.baeldung.chainedexception.exceptions.MainClass
      .getLeave(MainClass.java:34)
    at com.baeldung.chainedexception.exceptions.MainClass
      .main(MainClass.java:29)
Exception in thread "main" com.baeldung.chainedexception.exceptions.
  NoLeaveGrantedException: Leave not sanctioned.
    at com.baeldung.chainedexception.exceptions.MainClass
      .getLeave(MainClass.java:37)
    at com.baeldung.chainedexception.exceptions.MainClass
      .main(MainClass.java:29)

2つの例外のログはでていますが、関係性が全く示されておらず、別々に発生したかのようにも見えてしまいます。これではどのエラーについて対処すべきか、すぐに的確に判断することはできません。

例外をチェーンした例

では、次に例外をチェーンした例を見てみましょう。

このコードでは、キャッチされたTeamLeadUpsetExceptionが次に投げられるNoLeaveGrantedExceptionの引数に渡されています。

public class MainClass {
    public void main(String[] args) throws Exception {
        getLeave();
    }
 
    public getLeave() throws NoLeaveGrantedException {
        try {
            howIsTeamLead();
        catch (TeamLeadUpsetException e) {
             throw new NoLeaveGrantedException("Leave not sanctioned.", e);
        }
    }
 
    public void howIsTeamLead() throws TeamLeadUpsetException {
        throw new TeamLeadUpsetException("Team lead Upset.");
    }
}

これに対するログは以下のようになります:

Exception in thread "main" com.baeldung.chainedexception.exceptions
  .NoLeaveGrantedException: Leave not sanctioned. 
    at com.baeldung.chainedexception.exceptions.MainClass
      .getLeave(MainClass.java:36) 
    at com.baeldung.chainedexception.exceptions.MainClass
      .main(MainClass.java:29) 
Caused by: com.baeldung.chainedexception.exceptions
  .TeamLeadUpsetException: Team lead Upset.
    at com.baeldung.chainedexception.exceptions.MainClass
  .howIsTeamLead(MainClass.java:44) 
    at com.baeldung.chainedexception.exceptions.MainClass
  .getLeave(MainClass.java:34) 
    ... 1 more

ただ2つの例外の情報が並べられるのではなく、複数の例外が発生した順番、そしてそれらの関係性もひと目で分かるようになっています。このログをみれば、対処すべきエラーがTeamLeadUpsetExceptionであることがすぐにわかります。

まとめ

このように、例外をチェーンしたほうが遥かにエラーの原因が突き止めやすいです。以前の僕は例外をキャッチして満足していましたが、これを知ってからはしっかりと例外はチェーンさせるように心がけています!みなさんもキャッチした例外はきちんと責任を持って次の例外につなげてあげるよう心がけましょう。