finally ブロック
いちばん記録が欲しい回だけ、ログが残らない
夜間バッチの取り込み処理に、開始と終了のログを入れました。
Java
System.out.println("取り込み開始");
importRows(path);
System.out.println("取り込み終了");翌朝ログを見ると、開始だけが記録されて終了が無い日があります。importRows の途中で例外が飛び、その先の行が実行されなかったのです。処理が成功した日はきれいに 2 行そろい、失敗した日だけ片方しか残りません。原因を追いたいのは失敗した日のほうなのに、手がかりがいちばん少なくなるわけです。
catch に同じ行を書き足せば直りますが、catch が 2 つ 3 つと増えれば、そのすべてに同じ行をコピーすることになります。書き忘れた 1 つが、また同じ穴を開けます。
finally は、どの道を通っても必ず寄る
try には catch のほかに finally を付けられます。ここに書いた処理は、try を最後まで走り抜けたときも、catch に飛んだときも、必ず実行されてからメソッドの外に出ます。
Java
long start = System.currentTimeMillis();
try {
importRows(path);
System.out.println("取り込み成功");
} catch (RuntimeException e) {
System.out.println("取り込み失敗 " + e.getMessage());
} finally {
System.out.println("所要 " + (System.currentTimeMillis() - start) + " ミリ秒");
}成功でも失敗でも、所要時間が必ず 1 行残ります。分かれた道が最後に合流する場所、と考えると位置付けがはっきりします。
強いのは、try の中に return があっても通ることです。Java は返す値をいったん控えてから finally を実行し、そのあとで呼び出し元に返します。誰も catch していない例外で抜けていく場合も同じで、finally を通ってから外へ飛んでいきます。
ロックの解放、トランザクションの巻き戻し、実行回数の記録など、close() 一発では済まない後始末はここに置きます。ファイルや接続を閉じるだけなら、try-with-resources のほうが短く書けます。
finally に return を書くと、例外が消える
便利な場所なので、つい戻り値まで置きたくなります。これが最大の落とし穴です。
Java
public static int broken() {
try {
throw new IllegalStateException("本当の原因");
} finally {
return -1;
}
}呼び出し元に届くのは -1 だけです。IllegalStateException はどこにも現れません。finally の return が、飛んでいくはずだった例外を押し流してしまうからです。try の中に return があった場合も同じように上書きされます。
同じ理由で、finally の中で新しい例外を投げるのも避けてください。元の例外が上書きされて、本当の原因が消えます。どうしても例外になり得る処理を置くなら、その部分だけ try-catch で包んでログに残しておきます。
finally には後始末だけを書き、return は外に 1 つだけ置く。これを守るだけで、この手のバグはほぼ起きません。
要件
tryブロックの中でa / bを計算し、正常時は"result=" + 商の文字列を組み立てることArithmeticExceptionをcatchして、例外時は"error"の文字列を組み立てることfinallyブロック (または finally と同等の最終処理) を用いて、正常時・例外時のいずれでも戻り値の末尾に必ず",end"を付け加えること
入出力例
trackedDivide(10, 2) → "result=5,end"
trackedDivide(10, 0) → "error,end"
trackedDivide(9, 3) → "result=3,end"
trackedDivide(20, 4) → "result=5,end"
trackedDivide(7, 0) → "error,end"