きどたかのブログ

いつか誰かがこのブログからトラブルを解決しますように。

検査例外と非検査例外、あとエラー。

なんで正月にこんな事書いてるんだろうか・・・。


長年Javaをやっていても、「検査例外」と「非検査例外」の使い分けにコレと言った答えが出ない。


個人的には「検査例外」を好む。
誰かが作ったプログラムの「非検査例外」をcatchしないといけない状況に陥ると面倒臭い。
Javadocに書いてくれるなら「非検査例外」でもいいんだけど。


自分の大嫌いな言葉にAPIというのがある。
自分は「非検査例外」も含めて仕様だと捉えている。
APIっつーか、「仕様」とは何かを考えた時に、例外が起こることも仕様なのだから、
「検査例外」だけ仕様化されても困るということだ。


簡単な例だとInteger.parseInt(String)がNumberFormatExceptionを投げなくなったら
ほとんどのシステムは動かなくなるだろう。


通常RuntimeExceptionはJavadocに書かれることがほとんどないが、
Integer.parseInt(String)については、Javadocに明記されている。
Javadocだけでなく、シグニチャーとしてthrowsにも書かれている。


「非検査例外」についても仕様として明記されてないといけない。


「回復可能」か「回復不可能」という点で「検査例外」と「非検査例外」を分ける話はある。
しかし、ログ出力なり、画面へのエラー出力なりが求められることが多く、
より詳細な状況を伝えるために「非検査例外」をcatchせざるを得ない場合が多い。
呼ばれる側のクラスの設計(思い)のみで非検査例外にすべきかの決定をしても、
実際にそれが良い設計であったかを知ることは難しい。
また、作ってる会社・組織などが異なることで
「検査例外」「非検査例外」の設計方針の一貫性を保つことは不可能だと言える。


「検査例外」はtry-catchをするか、throwsに書くかが求められる。
try-catchで読みにくいコードになる話はあるのだが、
これは「機能」の話ではなく、「コードの保守」に関する点で論じられている。
try-catchばかりで読みにくいというのは確かにあるが、
その箇所でtry-catchせねば出せない情報があるのも確かだ。
出した方がいい情報を出さないのは阿呆のすることだ。
多くのデバッグをさせられる羽目になる人ってのは、
解析を可能とする意味のある例外を投げようという傾向が根付くのではないだろうか。


throwsで書かれている「検査例外」が変更になったら、コンパイルし直しだ。
全てのコードを管理しているのであれば、コンパイルの問題はCIなりで簡単に検出可能だろう。
誰かが提供しているJarが変わってthrowsが変わったことによりコンパイルエラーになった場合、
使っている側はなんと言うだろうか。大抵の場合、苦情を投げるだろう。
非推奨メソッドに変わった場合についても苦情があるかもしれない。


CheckStyleのJavadocMethodsでallowUndeclaredRTEというのはデフォルトfalseになってる。
Javadocに@throwsで書いて、throws宣言していないRuntimeExceptionを許すか否かだ。
「非検査例外」をJavadocに明記するのであれば、シグニチャーにも書くことを求めてる。


自分は「非検査例外」を文書化したいと考える性質なので、
Javadocに書きたい、しかしシグニチャーにまで入れたいかというとそうでもない。
シグニチャーに「非検査例外」が宣言されていることの意味はほとんど無い。
eclipseのF3で飛んでった時に分かりやすいくらいか。)
そういう場合、上記のCheckStyleはfalseにした方がいい。
まあ、実際そうしてるかというとやってないんだけど。。。
「検査例外」「非検査例外」に関しては関心が寄せられることも少なく、コンセンサスを得ることも難しい。
呼び出し先の「非検査例外」を全て記述すべきか否かという話になってしまう。
厳密な仕様という意味では、記述すべきということになってしまうだろう。
ただ、リファクタリングしているうちに、そのJavadocは陳腐化するのが目に見えているので、
せいぜいメソッド内で投げてる「非検査例外」をJavadocに書くという程度におさめるのが現実的だ。


メソッドの呼び出し側は、呼び出し先のメソッドのシグニチャーをクラスファイルに保存してはいるが、
throwsの箇所はシグニチャーとして保存されていない。
catchした場合、各メソッドのException Tableにその例外クラスは登場するのだが、
それはシグニチャーとして保存されているわけではない。
コードを書いていると、飛んでこない「検査例外」をcatchしようとすると通常コンパイラがエラーにするんだけど、
実行時にthrowsの箇所が合わなくても例外は起こらない。
「検査例外」が減っても大丈夫、「検査例外」が増えた場合は、catchされることなく呼び出し側へ例外が届く。


java.lang.Errorについては、普通はどうしようもない時に出るので通常はcatchしない。
ただ、catchしてはいけないということではない。
java.lang.Errorでcatchするのはやらないが、特定のErrorについてならやってもいい。
意外と実際の開発でもErrorはバンバン出力されている。
追加情報を出せそうな箇所は、一回catchしてログを出すようなことはある。
その後は別にre-throwでも構わない。
あくまで、これは開発時のデバッグの利便性という観点でコードを書いてるスタンスだ。