業務アプリケーションにおける例外によるエラー処理の実装案

 業務アプリケーションにおいてエラーが発生した場合には、


を行います。

このために、C言語等の戻り値でエラーを通知する言語のプログラムでは、関数の呼出しごとにエラーが発生していないかをその戻り値で判定して、エラーだった場合はこれらの処理を行うようにしています。

一方、Java等の例外によってエラーの発生を通知する機構を持つ言語では、エラーを戻り値等で通知するのではなく、エラーは例外で通知するのが一般的です。
また、例外の検知も、呼出しごとに行うのではなく、モジュール単位(場合によってはブロック単位)で行うのが普通です。

このように、例外によってエラーを通知する言語では、戻り値で通知する言語におけるそれとは異なるエラー処理の機構が必要となります。
そこで、そのような言語の代表である Java でのエラー処理の実装について考えてみたいと思います。

 本提案は、さまざまなスキルの不特定多数のプログラマが盲目的に従うことで一定の品質を実現しようというもので、個々の内容については必ずしも最適なものとはいえないことはやむなしとしています。


  1. 例外の通知(システム的エラー)

     0除算やIOエラーなどのシステム的エラーは、特にそれに対するコードを書かなくても Java がそれぞれの例外を通知してくれます。


  2. 例外の通知(アプリケーション的エラー)

     入力値の制限などのアプリケーション的エラーについては、その旨の例外を生成して通知(throw)しなければなりません。
    本提案では、アプリケーション的エラーに対する例外を1種類だけ設け、全てのアプリケーション的エラーに対してその例外を使うことにします。

    アプリケーション例外:AppException

    public class AppException extends RuntimeException
    {
    String detailInfo;

    public AppException()
    {
    super();
    }
    public AppException(String message)
    {
    super(message);
    }
    }

    使用例:

    if ( 0 == x ) {
    throw new AppException("Illegal Parameter");
    }


  3. 例外の通知(処理済み例外)

     本提案では、上記の例外の他に、単に呼び元にエラーが発生したことだけを伝えるための例外を設けることにします。
    この例外を検知した場合は、特に何もせず、そのままさらに呼び元に通知するだけとします。
    そして、最終的な呼び元(大抵はイベントリスナ)で、メッセージを表示するなり、終了するなりします。

    処理済み例外:ParsedException

    public class ParsedException extends RuntimeException
    {
    public ParsedException()
    {
    super();
    }
    public ParsedException(String message)
    {
    super(message);
    }
    }

    使用例:

    public void foo() throws ParsedException
    {
    try {
    ...
    } catch (ParsedException e) {
    throw e;
    } catch (Exception e) {
    throw new ParsedException(e.getMessage());
    }
    }


  4. 例外の検知

     例外を検知(catch)せず呼び元にそのまま渡すことも無くはありませんが、通常は、例外はそのメソッド内で検知して、それに対する処理を行います。

    そのとき、そのメソッドの処理全体をひとつの Try ブロックにして一括して例外を検知するのが普通です。
    (勿論、必要な場合は、メソッドの呼出し単位やある程度まとまった処理の単位で検知する場合もあります。)

    そこで、本提案では、


    とします。

    public void foo() throws ParsedException
    {
    try {
    ...
    } catch (ParsedException e) {
    throw e;
    } catch (Exception e) {
    throw new ParsedException(e.getMessage());
    }
    }

    特定の例外に対して特別な処理をしたい場合は、上記にとらわれず、その例外だけを検知して処理することを禁じることはしません。


  5. ログの出力

     エラー発生のログとしては、

    1. エラー情報(発生日時,エラー内容)
    2. エラー詳細情報(エラー発生時の設定値等,自由記述)
    3. 呼出し経路(StackTrace)

    の3種類を出力することとします。

    1. エラー情報(発生日時,エラー内容)

       例外を検知したときに出力します。
      呼出し経路を正しく戻っていくかを確認するために、処理済み例外を検知した場合も出力することにします。


    2. エラー詳細情報(エラー発生時の設定値等,自由記述)

       エラーの詳細を出力したいときに出力します。(任意)
      エラーの詳細がわかる箇所ということで、例外を検知したときではなく、エラーが発生した箇所で出力することになると思います。
      システム的例外に対して出力したい場合は、その部分だけの try ブロックを設けて行うことになります。(後述のプログラム例参照。)


    3. 呼出し経路(StackTrace)

       最初に例外を検知したときに1回だけ出力します。


    プログラム例:

    public class MyClass
    {
    private static final String CLASS = "MyClass";

    public void foo(int x) throws ParsedException
    {
    private final String METHOD = "foo";

    try {
    // パラメータ・エラー・チェック(詳細情報 x=0 を出力)
    if ( 0 == x ) {
    LogWriter.doWrite(CLASS, METHOD, "Illegal Parameter", "x=0");
    throw new AppException("Illegal Parameter");
    }

    // 特にエラー詳細を出力したい場合(詳細情報 x を出力)
    try {
    uoo(x);
    } catch (Exception e) {
    LogWriter.doWrite(CLASS, METHOD, e.getMessage(), "x="+new Integer(x).toString());
    throw e;
    }

    // 通常の例外処理をする場合
    uoo(x);

    } catch (ParsedException e) {
    LogWriter.doWrite(CLASS, METHOD, e.getMessage());
    throw e;
    } catch (Exception e) {
    LogWriter.doWriteStackTrace(e);
    LogWriter.doWrite(CLASS, METHOD, e.getMessage());
    throw new ParsedException(e.getMessage());
    }
    }

    public void boo(int x) throws ParsedException
    {
    private final String METHOD = "boo";

    try {
    foo(x);

    } catch (ParsedException e) {
    LogWriter.doWrite(CLASS, METHOD, e.getMessage());
    throw e;
    } catch (Exception e) {
    LogWriter.doWriteStackTrace(e);
    LogWriter.doWrite(CLASS, METHOD, e.getMessage());
    throw new ParsedException(e.getMessage());
    }
    }

    public static void main(String[] args)
    {
    private final String METHOD = "main";

    try {
    boo(new Integer(args[0]).intValue());
    } catch (ParsedException e) {
    LogWriter.doWrite(CLASS, METHOD, e.getMessage());
    System.out.println("Error occured !!");
    } catch (Exception e) {
    LogWriter.doWriteStackTrace(e);
    LogWriter.doWrite(CLASS, METHOD, e.getMessage());
    System.out.println("Error occured !!");
    }
    }
    }

    LogWriterクラス は、そういうものが作ってあると思って下さい。

    この例で foo()x=0 が渡された場合のログは、次のようになります。

    Thu Aug 15 16:52:11 JST 2002: MyClass.foo() Illegal Parameter x=0
    java.lang.Exception: Illegal Parameter
    at MyClass.foo(MyClass.java:53)
    at MyClass.boo(MyClass.java:75)
    at MyClass.main(MyClass.java:92)
    Thu Aug 15 16:52:11 JST 2002: MyClass.foo() Illegal Parameter
    Thu Aug 15 16:52:11 JST 2002: MyClass.boo() Illegal Parameter
    Thu Aug 15 16:52:11 JST 2002: MyClass.main() Illegal Parameter


  6. 回復処理,終了処理

     操作者への通知や回復処理,終了処理は、原則として、最終的な呼び元(大抵はイベントリスナ)で行うこととします。