第1章
はじめてのデザインパターン  

第2章
逆引きカタログ ロジック編

第3章
逆引きカタログ J2EE編

第4章
逆引きカタログ その他

第5章
デザインパターン適用の勘所

第4章 逆引きカタログ その他

Authors:Agata Toshitaka

実行時例外を標準的に使う

 
イントロダクション
パターン解説
具体的な例
まとめ
 

イントロダクション

  ここまでは今までのJavaの常識からはありえない「実行時例外を使いましょう」という考え方をご紹介します(ちなみにこれは『実践J2EEデザイン』という書籍で紹介されたイディオムで、デザインパターンではありません)。
 筆者も「実行時例外を使おう」と初めて聞いたときはたいへん驚きました。今までの常識が崩れ去った気分でした。 しかし、今ではこの方法で、問題なく、以前よりも効率良くアプリケーションを書いています。
TOP

パターン解説

 例外には大きく分けて検査例外と実行時例外があります。
 検査例外はjava.lang.Exceptionを継承した例外です。 検査例外が発生する場所では、必ずcatchブロックで例外をキャッチするか、throws句で例外をメソッドの呼び出し元に投げる宣言をする必要があります。 実行時例外はjava.lang.RuntimeExceptionを継承した例外です。 実行時例外は検査例外と違い、キャッチする必要はありません。
 今までのJavaの常識は「検査例外を使いましょう。そうすれば堅牢なアプリケーションが書けますよ」でした。 確かに、検査例外を使うことで、メソッドの呼び出し元に例外のキャッチを強制することができ、「例外処理を書き忘れた」ということをなくせます。
 しかし、みなさんの周りのアプリケーションを見てみてください。 あちこちに例外をキャッチするためのコードが書かれていて、しかもそのほとんどは「例外を別の例外に変換して投げ直すだけ」ということが多いと思います。 このようなルーチンワーク的な例外処理に意味はあるのでしょうか? また「throwsApplicationException」のように多くのメソッドで「検査例外出ます。キャッチよろしく」と宣言されます。 これはインタフェースと相性がよくありません。 さまざまな実装が予想されるインタフェースのメソッドに「ハードコード」されたthrows句は、後々の汎用性の低下につながる場合があります。
 結局、「呼び出し元が例外を処理できる」場合のみ検査例外を使い、それ以外はすべて実行時例外を使ったほうが、アプリケーションのコードがずいぶんシンプルになるということです。 特に通常のWebアプリケーションでは、例外をキャッチして何らかの処理ができることは稀でしょう。
 途中のクラスで例外処理をする必要はなく、サーブレットなど大本の呼び出し元で「RuntimeException」をキャッチするだけでオッケーです。
TOP

具体的な例

 それでは検査例外を使ったコードと、実行時例外を使ったコードの具体的な例を比較してみましょう。
 検査例外を使った場合のコード(リスト6,リスト7,リスト8,リスト9)では、LogicAクラスのexecute()メソッドでDaoAクラスから投げられるSQLExceptionをキャッチして、ApplicationExceptionに変換して投げ直しています。
 DaoAクラスのupdate()メソッドを使うほとんどの場所で、このような中途半端な例外処理を書く必要が出てきます。
リスト6 ServletA.java(検査例外版)

public class ServletA extends HttpServlet {

	/** ログです。*/
	Log log = LogFactory.getLog(ServletA.class);
	protected void doGet(HttpServletRequest req, HttpServletResponse res)
				throws ServletException, IOException {

		try {
			// ロジックの実行
			LogicA logic = new LogicA();
			logic.execute();
		} catch (ApplicationException e) {
			log.error(e.getMessage(), e);
			// エラー画面へ
		}
	}
}
リスト7 LogicA.java(検査例外版)

/**
 * 業務ロジックです。
 */
public class LogicA {
	void execute() throws ApplicatonException {
		try {
			// 更新処理の実行
			DaoA dao = new DaoA();
			dao.update();
		} catch (SQLException e) {
			// 例外の詰め直し!
			throw new ApplicationException(e);
		}
	}
}
リスト8 DaoA.java(検査例外版)

/**
 * データアクセスオブジェクトです。
 */
public class DaoA {
	/** コネクションです。*/
	Connection conn;
	// SQLExceptionの発生の可能性あり!
	void update() throws SQLException {
		conn.createStatement();
	}
}
リスト9 ApplicationException.java(検査例外版)

/**
 * アプリケーション例外(検査例外)です。
 */
public class Application extends Exception {
	public ApplicationException(Throwareble t) {
		super(t);
	}
}
 実行時例外を使った場合のコード(リスト10,リスト11,リスト12,リスト13)では、DaoBクラスのupdate()メソッド内で、SQLExceptionをその場でキャッチして、実行時例外SQLRuntimeExceptionに変換して投げています。 実行時例外ですので、LogicBクラスのexecute()メソッドではキャッチの必要もなくシンプルなコードになります。
 ロジッククラスのexecute()メソッドから「throwsApplicationException」が消えたのもポイントです。 これにより、ロジッククラスのexecute()メソッドを呼び出すコードもシンプルになるはずです。
 最終的にSQLRuntimeExceptionはServletBでキャッチされて、エラー画面へ遷移という「意味のある例外処理」が行われることになります。
リスト10 ServletB.java(実行時例外版)

public class ServletB extends HttpServlet {

	/** ログです。*/
	Log log = LogFactory.getLog(ServletB.class);
	protected void doGet(HttpServletRequest req, HttpServletResponse res)
				throws ServletException, IOException {

		try {
			// ロジックの実行
			LogicB logic = new LogicB();
			logic.execute();
		} catch (RuntimeException e) {
			log.error(e.getMessage(), e);
			// エラー画面へ
		}
	}
}
リスト11 LogicB.java(実行時例外版)

/**
 * 業務ロジックです。
 */
public class LogicB {
	void execute() {
			// 更新処理の実行
			// 例外処理がなくなりシンプルに!
			DaoB dao = new DaoB();
			dao.update();
	}
}
リスト12 DaoB.java(実行時例外版)

/**
 * データアクセスオブジェクトです。
 */
public class DaoB {
	/** コネクションです。*/
	Connection conn;

	void update() {
		try {
			// SQLExceptionの発生の可能性あり!
			conn.createStatement();
		} catch (SQLException e) {
			// 検査例外を実行時例外に変換する
			throw new SQLRuntimeException(e);
		}
	}
}
リスト13 SQLRuntimeException.java(実行時例外版)

/**
 * SQL実行時例外(検査例外)です。
 */
public class SQLRuntimeException extends RuntimeException {
	public SQLRuntimeException(SQLException e) {
		super(e);
	}
}
TOP

まとめ

◎ほんとに実行時例外で大丈夫なの?

 大丈夫です。 実行時例外をアプリケーション全般に使用したほうがよいかどうかはアプリケーションの特性によって異なります。 通常のWebアプリケーションならば、実行時例外の使用は問題がないはずです。
 ただし、2つのメソッドをまたぐトランザクションのロールバックなどに関しては、工夫する必要があるでしょう。

◎Exception→RuntimeException

 1つの検査例外に対応した実行時例外を作りましょう。
 筆者は、検査例外のクラス名のExceptionの部分をRuntimeExceptionに変更した実行時例外クラスを作って使用しています。
 たとえば「ClassNotFoundException」なら「ClassNotFoundRuntimeException」です。 もちろんClassNotFoundExceptionが発生する場所では、その場でキャッチしFoundRuntimeExceptionに変換して投げ直します。 だって、ClassNotFoundExceptionを呼び出し元に返したって、何もできるはずありませんもんね。
TOP