無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

「エラーの原因ごとに例外の型を別ける」のはどうなの?

Java-APIでの例外設計について。
「エラーの原因ごとに例外の型を別ける」のはどうなの? という話です。個人的にはあんまり意味ないし、クラス書くのが面倒という点で好きではありません。

エラーの原因ごとに例外の型を分ける

API内で発生するエラーの原因ごとに、スローする例外の型を別け、それにより利用者が原因の切り別けと処理を行えるようにするアプローチです。

例えば「hogeメソッドで、原因の違うエラーが3つ発生しうる場合、APIを以下のように宣言します。

 void hoge() throws AException, BException, CException;

呼び出し側は次ように書きます。

try {
  hoge();
} catch ( AException e ) {
  // AExceptionに対する処理
} catch ( BException e ) {
  // BExceptionに対する処理
} catch ( CException e ) {
  // CExceptionに対する処理
}
このアプローチのメリット
  • 例外を追加した場合に、それがわかる。
    • 利用者側のコードがコンパイルエラーとなるので、例外の追加がわかり対処できる。

ふむ。

でも、例外が10コになったらどうなの

10コくらいならまじめに処理するもやぶさかではありません。

try {
  hoge();
} catch ( AException e ) {
  // AExceptionに対する処理
} catch ( BException e ) {
  // BExceptionに対する処理
} catch ( CException e ) {
  // CExceptionに対する処理
} catch ( DException e ) {
  // DExceptionに対する処理
} catch ( EException e ) {
  // EExceptionに対する処理
} catch ( FException e ) {
  // FExceptionに対する処理
} catch ( GException e ) {
  // GExceptionに対する処理
} catch ( HException e ) {
  // HExceptionに対する処理
} catch ( IException e ) {
  // IExceptionに対する処理
} catch ( JException e ) {
  // JExceptionに対する処理
} 

さらに、APIを3回使うとどうなるの

try {
  hoge();
} catch ( AException e ) {
  // AExceptionに対する処理
} catch ( BException e ) {
  // BExceptionに対する処理
} catch ( CException e ) {
  // CExceptionに対する処理
} catch ( DException e ) {
  // DExceptionに対する処理
} catch ( EException e ) {
  // EExceptionに対する処理
} catch ( FException e ) {
  // FExceptionに対する処理
} catch ( GException e ) {
  // GExceptionに対する処理
} catch ( HException e ) {
  // HExceptionに対する処理
} catch ( IException e ) {
  // IExceptionに対する処理
} catch ( JException e ) {
  // JExceptionに対する処理
} 

try {
  hoge();
} catch ( AException e ) {
  // AExceptionに対する処理
} catch ( BException e ) {
  // BExceptionに対する処理
} catch ( CException e ) {
  // CExceptionに対する処理
} catch ( DException e ) {
  // DExceptionに対する処理
} catch ( EException e ) {
  // EExceptionに対する処理
} catch ( FException e ) {
  // FExceptionに対する処理
} catch ( GException e ) {
  // GExceptionに対する処理
} catch ( HException e ) {
  // HExceptionに対する処理
} catch ( IException e ) {
  // IExceptionに対する処理
} catch ( JException e ) {
  // JExceptionに対する処理
} 

try {
  hoge();
} catch ( AException e ) {
  // AExceptionに対する処理
} catch ( BException e ) {
  // BExceptionに対する処理
} catch ( CException e ) {
  // CExceptionに対する処理
} catch ( DException e ) {
  // DExceptionに対する処理
} catch ( EException e ) {
  // EExceptionに対する処理
} catch ( FException e ) {
  // FExceptionに対する処理
} catch ( GException e ) {
  // GExceptionに対する処理
} catch ( HException e ) {
  // HExceptionに対する処理
} catch ( IException e ) {
  // IExceptionに対する処理
} catch ( JException e ) {
  // JExceptionに対する処理
} 

ギャー。しかもメイン処理は3行しかないですよ。
ここで、まともな神経のプログラマなら、こうしたくなると思うわけです。

try {
  hoge();
} catch ( Exception e ) {
  handleException(e);
} 

try {
  hoge();
} catch ( Exception e ) {
  handleException(e);
} 

try {
  hoge();
} catch ( Exception e ) {
  handleException(e);
} 

...

void handleException( Exception e ) {
  if ( e instanceof AException ) {
      // AExceptionに対する処理
  } else if ( e instanceof BException ) {
      // BExceptionに対する処理
  } else if ( e instanceof CException ) {
      // CExceptionに対する処理
  } else if ( e instanceof DException ) {
      // DExceptionに対する処理
  } else if ( e instanceof EException ) {
      // EExceptionに対する処理
  } else if ( e instanceof FException ) {
      // FExceptionに対する処理
  } else if ( e instanceof GException ) {
      // GExceptionに対する処理
  } else if ( e instanceof HException ) {
      // HExceptionに対する処理
  } else if ( e instanceof IException ) {
      // IExceptionに対する処理
  } else if ( e instanceof JException ) {
      // JExceptionに対する処理
  }
  throw new RuntimeException(e); // 想定外の例外が発生。バグ。
}

だいぶすっきり。
でもこうした場合、例外の原因ごとに型を別けた意味ないんじゃね?呼び出し元はExceptionでcatchしちゃってるので、例外追加してもコンパイルエラーになりません。だったらエラーコードでいいんでは。

try {
  hoge();
} catch ( Exception e ) {
  handleException(e);
} 

try {
  hoge();
} catch ( Exception e ) {
  handleException(e);
} 

try {
  hoge();
} catch ( Exception e ) {
  handleException(e);
} 

...

void handleException( Exception e ) {
  swithc ( e.getErrorCode() ) {
      case ErrorCode.A :
          // AExceptionに対する処理
          break;
      case ErrorCode.B :
          // AExceptionに対する処理
          break;
      ... 以下略
  
  }
  throw new RuntimeException(e); // 想定外の例外が発生。バグ。
}

エラーコード方式にすれば原因の追加もすぐにできます。とはいえ、例外もツールを使えばすぐに作れたりするので、そこは好きずきですが。

まとめ

API考えるときに、それがどういう風に使われるか、利用者がどのようコードを書くことになるか、イメージしながら設計しないとへましますね、という話でした。せっかく工数かけて例外切り分けたのに実は意味なかったりすると悲しいモノがあります。ご注意。