【Java進化史 第8回】Java7 〜例外を“正確に投げ直せる”ようになった日〜
昔、本当に例外処理ばかり書いていた。
正常処理より catch の方が長い。
そんなクラスも珍しくなかった。
とにかく捕まえる。
とにかく投げる。
とにかく throws を増やす。
そして気がつけば、メソッドの宣言はこうなる。
public void process() throws Exception
もう何でもありだ。
「結局、何が飛んでくるの?」
呼び出し側はこう書く。
try {
process();
} catch (Exception e) {
// で、何が来るの?
}
IOException?
SQLException?
それとも別の何か?
結局、process() を読む。
さらにその中で呼ばれているメソッドも読む。
これをカプセル化と呼べるのか。
メソッドのシグネチャを見れば契約が分かる。
それがオブジェクト指向の約束ではなかったのか。
だが現実は違った。
throws Exception は便利だった。
便利だが、それは「設計の曖昧さ」を広げていただけだった。
■ Java6までの“濁り”
例えば、こんなコード。
public void process() throws Exception {
try {
readFile(); // throws IOException
} catch (Exception e) {
throw e;
}
}
readFile() は IOException しか投げない。
だが catch (Exception e) と書いた瞬間、
コンパイラはこう判断する。
「Exception が投げられる可能性がある」
だから throws Exception になる。
正確ではない。
広がっている。
そして、その広がりは呼び出し側へ伝染する。
設計が、少しずつ濁っていく。
■ Java7の“地味な革命”
Java7で導入されたのが、
precise rethrow(例外の再スローの型推論) だ。
同じコードでもこう書ける。
public void process() throws IOException {
try {
readFile(); // throws IOException
} catch (Exception e) {
throw e;
}
}
一見、何も変わっていない。
だがコンパイラが変わった。
Java7はこう考える。
「tryブロックの中で、実際に投げられる例外型は何か?」
readFile() が投げるのは IOException だけ。
だから再スローされる型も IOException だけだと判断する。
その結果、
throws IOException
で済む。
例外が“正確になる”。
■ 何が嬉しいのか
- throws が広がらない
- APIが引き締まる
- 呼び出し側が迷わない
- シグネチャが契約として機能する
呼び出し側はこう書ける。
try {
process();
} catch (IOException e) {
// 何が来るか分かっている
}
読まなくていい。
追いかけなくていい。
契約が戻ってくる。
これは小さいが、本質的な改善だ。
■ それでも万能ではない
catch 変数を再代入すると、この推論は効かない。
catch (Exception e) {
e = new Exception(); // 再代入すると従来通り広がる
throw e;
}
コンパイラが追跡できる範囲でのみ働く。
魔法ではない。
だが思想は明確だ。
「正確に宣言せよ」
■ 例外はどこで処理するのか
- 発生箇所 → 技術的例外をそのまま投げる
- 中間層 → 無理に握りつぶさない
- 最上位 → ログ、通知、終了判断
Java6時代、広がった throws Exception は、その責任を曖昧にした。
Java7のprecise rethrowは、例外を“適切な型で上に渡す”ことを助ける。
それはつまり、責任の所在を明確にすることだ。
■ あの頃の自分に言いたい
Java7の再スローは地味だ。
だが、例外に振り回されてきた世代にとっては、 設計を少しだけ取り戻すための機能だった。
派手な進化ではない。
だが、確かに“進化”だった。
地味だけど、確実に設計や実装の質を上げる「例外の再スローの型推論」。
こういう機能、好きだなぁ。
0 件のコメント:
コメントを投稿