チェック例外の対処方法
Javaの特徴の一つにチェック例外があります。
IOException や SQLException なんかが代表例です。
自分がチェック例外に遭遇した時にどのように対処しているかをまとめてみました。
throwsする
public String read(File file) throws IOException
最も多く行うのが、何もせずに throws するパターンです。
eclipse の自動補正機能も、真っ先にこれを提示してくれます。
ただし、throws を書く前に一つだけ注意していることがあります。
「閉じるべきリソースがないかどうか」という点です。
他人のソースを読んでいると、ファイルを開いているけど、閉じていないというパターンをよく見かけます*1。
これをやるとGCに回収されるまで開きっぱなしになります。前にこれが原因で「例外が発生したらファイルを削除するつもりが、開きっぱなしのせいで削除されない」という問題が発生したことがありました*2。
なので、eclipse が throws を追加するように提案してきても、とりあえず無視して try...finally 句を書くべきかどうか確認するようにしています*3。
catchする
try{ (省略) }catch(IOException e){ // 例外からの復帰処理 }
次に多く行うのが、catch するパターンです。
こちらも、eclipse の自動補正機能が提示してくれます。
ただし、こちらも書く前に注意していることがあります。
「本当にキャッチすべきかどうか」という点です。
これに関しては、一つの判断基準を持っています。
それは「ここで例外をキャッチすることによって、例外から完全に復帰することができるか」かという点です。
- ここで例外をキャッチしても、後でもう一度例外が発生したかどうかによって処理を変えなければいけないのなら、throws します。
- ここで例外をキャッチすることにより、その後は例外の有無にかかわらず同じ処理ができるのなら、catch します。
以前読んだソースに、例外が発生したら異常を示す戻り値を返す、というメソッドがありました。
そのメソッドを呼んだ方は、たとえ対処することができなくても、戻り値を確認する必要に迫られていました。
そして、確認した後にやっていたことは異常を示す戻り値を返すことで、そのメソッドを呼んだ方は・・・(以下略)。
そうなるぐらいならば、最初からそのまま例外を投げて、処理すべきところまでキャッチしないのがベストです*4。
rethrow する
try{ (省略) }catch(IOException e){ // ファイルが読み込めず、メールを送信できなかった throw new SendFailedException("File can't be read.", e); }
忘れがちな対処方法が rethrow する (例外を別の例外に変換する) パターンです。
フレームワークで返すべき例外が決まっている時や、例外を抽象化したい時に使っています。
また、そもそも例外が絶対に発生しないことが分かっているときにも使います。
try { str.getBytes("ISO-8859-1"); } catch (UnsupportedEncodingException e) { // ISO-8859-1 を指定した場合、この例外は発生しない throw new Error(e); }
String.getBytes(String charsetName) メソッドは UnsupportedEncodingException を宣言していますが、UTF-8 や ISO-8859-1 が指定された場合、例外を投げることはありません。
(Charsetの仕様で、「Java プラットフォームの実装は、すべて次の標準文字セットをサポートする必要があります。(中略)UTF-8, ISO-8859-1・・・」と定義されています)
キャッチするふりをして何もしないのも一つの手ですが、それでは万が一システム異常が発生した時に問題を解析できなくなるので、Errorにして投げます。
絶対に発生しない例外をエラーへ変換することは、JavaのAPIの中でもたびたび行っています。
それどころか、StringCoding.encode() では ISO-8859-1 でデコードしようとしてUnsupportedEncodingException が発生した場合、 System.exit(1) を呼んでプログラムを終了させています。
・・・さすがに、これを自分でやるのはためらうので、やりません。
難しい?
チェック例外は、正しく使えばプログラムを分かりやすく記述するいい方法だと思っています。
以前、「例外は難しくふつうは使いこなせない」ということを言われましたが、ここに書いたような方法でたいていは対処できると思います。
*1:サンプルソースをそのままコピペしていることが原因のようです。その手の奴は説明のために例外処理を削っていることが多いのでよく確認して使うようにしています。
*2:File.delete() は削除に失敗しても例外を出さないので、気付かれずに放置されていました。
*3:これを促すためにチェック例外があるんだと思います。
*4:ちなみに、全てのメソッドに catch(Exception e) を書いて、例外が発生したら意地でも戻り値にするというソースを扱ったことがあります。その中で例外が発生したときは、解析にものすごい時間と手間とやる気を取られました(今は例外ブレークポイントで対処しています)。