例外処理する際の規定
try,catch,finallyを際の個人的に思う(か、広く守られている)規定についてまとめてみた。
ほか、守るべき内容があれば、コメントください。
try/catchはループ外にする
Exception生成は無料ではない。
int sum = 0;
for (int i = 0; i < 99900000; i++) {
try {
sum += i;
} catch (Exception ex) {
}
}
int sum = 0;
try {
for (int i = 0; i < 99900000; i++) {
sum += i;
}
} catch (Exception ex) {
}
時間を計測してみた結果、役21%の性能差がある。
for内:1312
for外:1042
JIT(Just In Time)を無効にしないと、コードが最適化されるため、差はなくなる。
JIT無効オプション:-Djava.compiler=none
例外を投げる前にオブジェクトを有効な状態に戻す
例外が発生したオブジェクトは使い続ける可能性がある。
private static class A {
private final int 最大サイズ = 10;
private int 現在のサイズ = 0;
String[] values = new String[20];
public void add(String val) throws Exception {
現在のサイズ++;
if (現在のサイズ > 最大サイズ) {
throw new Exception("最大サイズ超えた");
}
values[現在のサイズ - 1] = val;
}
}
現在のサイズ++;
if (現在のサイズ > 最大サイズ) {
throw new Exception("最大サイズ超えた");
}
- 例外は投げたが、このオブジェクトの最大サイズは
最大サイズ + 1
のままである。 -
values[現在サイズ]
を実行した場合、不正処理になる可能性がある。
例外処理でプロセスを制御するな
正常処理で制御できるプロセスは、例外で処理すべきではない。
- 可読性が落ちる
- 例外処理はコストがかかる
private static void showWithEx(String[] src) {
try {
int i = 0;
while (true) {
String s = src[i++];
}
} catch (ArrayIndexOutOfBoundsException ex) {
}
}
private static void show(String[] src) {
int i = 0;
while (i < src.length) {
String s = src[i++];
}
}
例外のコスト計測
public static void main(String[] args) {
final int COUNT = 100000;
String[] src = {"ab", "cd", "ef"};
long start = 0L;
start = System.currentTimeMillis();
for (int i = 0; i < COUNT; i++) {
showWithEx(src);
}
System.out.println(System.currentTimeMillis() - start);
start = System.currentTimeMillis();
for (int i = 0; i < COUNT; i++) {
show(src);
}
System.out.println(System.currentTimeMillis() - start);
start = System.currentTimeMillis();
for (int i = 0 ; i < COUNT; i++ ) {
Object o = new ArrayIndexOutOfBoundsException();
}
System.out.println(System.currentTimeMillis() - start);
}
0
156
63
- 改修後は、回数をいくら増やしても0だった。
- 改修前は156かかった
- new ArrayIndexOutOfBoundsException()は、63なので、生成以外のコストも無視できないね
膨大なtryは使うな
処理を全部一つのtryに入れがちで、一見綺麗、シンプルに見えるが、実は罠である。
- 「コードが長い = 例外が発生する可能性も高い」ので、どこで発生した例外なのかデバックし辛い
- 膨大なtryは、catch節も増え、それらのロジック関係も解決しないといけない
public static void main(String[] args) {
try {
// 略
String src;
src = "123";
int val1 = Integer.parseInt(src);
src = "abc";
int val2 = Integer.parseInt(src);
// 略
src = null;
src.getBytes();
} catch (NumberFormatException ex) {
System.out.println(ex.getMessage());
} catch (NullPointerException ex ) {
System.out.println(ex.getMessage());
}
}
Catch all使うな
上位例外クラス(Throwable)一つでcatchすることはNG。
- 各種例外に対して同じ対処しかできない。
catch内でif、instanceOfを使って処理は分けれるが・・・やめて・・・ - RuntimeExceptionのような、プログラム中止すべき場合もcatchされて、
誰も知らないまま制圧されちゃう
try {
// Checked Exception
File f = new File("存在しない");
int b = new FileInputStream(f).read();
// Runtime Exception
Object o = null;
o.getClass();
} catch (Throwable ex) {
ex.printStackTrace();
}
例外を隠すな
catch,finallyが例外を投げた場合、例外内容が隠される場合がある。
public static void main(String[] args) {
try {
m1();
} catch (Exception ex) {
System.out.println("In main : " + ex.getMessage());
}
}
private static void m1() throws Exception {
try {
System.out.println("m1 : try");
throw new Exception("First EX");
} catch (Exception ex) {
System.out.println("m1 : catch");
throw new Exception("Second EX");
} finally {
System.out.println("m1 : finally");
throw new Exception("Third EX");
}
}
m1 : try
m1 : catch
m1 : finally
In main : Third EX.
根本原因はthrow new Exception("First EX");
なのに、
拾ったのはthrow new Exception("Third EX");
である。
例外を無視するな
例外をcatchしたが、「なにもしない若しくはログだけ出力する」のは、隠蔽工作であり
プログラムに健全性に大きな影響を与える。
try {
File f = new File("存在しない");
int b = new FileInputStream(f).read();
} catch (FileNotFoundException ex) {
} catch (IOException ex) {
}
適切な例外処理
- Checked例外に関しては、プログラム内で修復すべき
- 修復できない例外は、throwsを使って呼び出し元へ修復を依頼する
おまけ
(常用)例外クラス体系
例外クラスの分類
- Runtime Exception
- RuntimeExceptionか継承したクラス
- プログラムで修復不可能だと見られる例外
- catchしなくてよい
- Checked Exception
- RuntimeException以外のクラス
- プログラムで修復可能だと見られる例外
- catchもしくはthrowsするように強制されちゃう