multi-catchを信じていいのか?

みなさんお元気ですか?
もうじきクリスマスですが、今年の僕にはクリスマスがあります。
だって今年はあずにゃんと一緒だから。


まあ、そんなのろけ話はどうでもいいとして、
Java7から新機能として実装されたmulti-catchですよ。multi-catch


例外処理のリファクタリングをしていてこんな疑問が沸きました。


この辺りですごく混乱してるのが見て取れます。

@bleisさんの指摘がなかったら本当にそのまま思い込んでいるところでした。
間違いを正していただき本当にありがとうございます。




まとめる意味もこめて、各所で散々議論され尽くされ、もう精も根も尽き果てている例外さんですがもう一回復習してみます。

検査例外ってなぁに?

  • Exceptionをextendsしている例外クラス。


Exception クラスとそのサブクラスは、通常のアプリケーションでキャッチされる可能性のある状態を示す Throwable の形式の 1 つです。

  • この例外をthrowするメソッドを呼び出す側はtry-catchするかthrows句で指定しなければならない。漏れていたらコンパイルエラーとなる。
  • 例外処理を行っていることをコンパイラが「検査する」。だから検査例外。
  • try-catchもしくはthrows句を強制するため、必ず例外処理を書かなければならない。この性質から検査例外は復帰可能な例外と考える。

非検査例外ってなぁに

  • RuntimeExceptionをextendsしている例外クラス。


メソッドの実行中にスローされるがキャッチされない RuntimeException のサブクラスについては、メソッドの throws 節でそれらを宣言する必要はありません。

  • この例外をthrowするメソッド呼び出しても呼び出す側はtry-catchをしなくてもよい。throws句を記述する必要はない。
  • RuntimeException自体はExceptionのサブクラスであるが、コンパイラは例外的に「検査しない」。だから検査例外。
  • 非検査例外は例外処理を強制されない為、実行するまで何が起きるか分からない。この性質から基本的には復帰できない例外と考える。
    • 故に、実行時例外なんていう言い方もする。


大体こんな感じ。至極簡単にまとめると

  • 検査例外はちゃんと例外処理しないとコンパイラが怒る。ちゃんと処理したら復帰して良いよ!
  • 非検査例外は例外処理しなくてもおk。その代わりちゃんとプログラム落とせよ。

multi-catchってなぁに

で、こっからが本番です。
multi-catchとはProject Coinで策定されたJava7からの新機能です。

Java1.6以前はこんな書き方でした。

try {
	Document doc
		=DocumentBuilderFactory.newInstance()
		.newDocumentBuilder()
		.parse(new FileInputSream(filename));
(省略)

} catch (IOException e) {
	(ログの出力や復帰処理など。)
} catch (IllegalArgumentException e) {
	(ログの出力や復帰処理など。)
} catch (ParserConfigurationException e) {
	(ログの出力や復帰処理など。)
} catch (SAXException e) {
	(ログの出力や復帰処理など。)
}

非常にもっさり



ソースに漂うもっさり感を取り去る為Java7からこんな書き方が出来るようになりました。

System.out.println("hoge");
try {
	Document doc
		=DocumentBuilderFactory.newInstance()
		.newDocumentBuilder()
		.parse(new FileInputSream(filename));
(省略)

} catch (IOException
	| IllegalArgumentException
	| ParserConfigurationException
	| SAXException e) {
	(ログの出力や復帰処理など。)
}
System.out.println("fuga");

かなりすっきりしました。



でも、ちょっと待って欲しい。
try-catchされる例外は基本的に復帰可能な例外です。
つまり、例外が起きたとしてもちゃんと処理するならばプログラムの実行を許される契約です。

System.out.println("hoge");
try {
	Document doc
		=DocumentBuilderFactory.newInstance()
		.newDocumentBuilder()
		.parse(new FileInputSream(filename));
(省略)

} catch (IOException e) {
	(ログの出力や復帰処理など。)
} catch (IllegalArgumentException e) {
	(ログの出力や復帰処理など。)
} catch (ParserConfigurationException e) {
	(ログの出力や復帰処理など。)
} catch (SAXException e) {
	(ログの出力や復帰処理など。)
}
System.out.println("fuga");


以上の例ではhogeが出力された後、ちゃんとfugaが表示されることが期待できます。
fugaが表示されない場合は、catch句throwし呼び出し元で例外処理されることを期待するときや、例外が起きることを想定してcatch句内部でreturnしたときです。
つまり、catch句はif文のように振舞うわけでそれぞれの例外に対応した処理を書かないといけないんじゃないの?と思うわけです。


multi-catchを利用することによって様々な例外を一括処理することが許されるようになりましたが、様々な例外が一括して処理されるような状況ってよもやthrowするかログの出力くらいしかありません。
散々忌み嫌われてきたコレにも似た状況に陥るんじゃないかと思ってしまうくらいです。

try {
(省略)
} catch (Exception e) {
	e.printStackTrace();
}

まとめ

正直なところ、multi-catch使うときはちゃんと例外の契約に沿っているのかを熟考して使わないいけないという感想を抱きました。
そうでないと復帰させれるはずの例外までmulti-catchで丸めてしまい、バグの温床*1になるんじゃないかとおっかなびっくり。

             /)
           ///)
          /,.=゙''"/
   /     i f ,.r='"-‐'つ____   こまけぇこたぁいいんだよ!!
  /      /   _,.-‐'~/⌒  ⌒\  糖衣構文なんだから!!
    /   ,i   ,二ニ⊃( ●). (●)\
   /    ノ    il゙フ::::::⌒(__人__)⌒::::: \
      ,イ「ト、  ,!,!|     |r┬-|     |
     / iトヾヽ_/ィ"\      `ー'´     /

では許されないような気がします。


参考元


Effective Java 第2版 (The Java Series)

Effective Java 読書会 13 日目 「Java の例外めんどくさい」

Throwableについて本気出して考えてみた

Throwableについて本気出して考えてみた 2nd Season

Java SE 7徹底理解 第1回 言語仕様の小さな変更 - Project Coin

*1:Javaって堅牢な設計にするっていうアレはどこ行った…。