完璧というのはなかなか難しいもので、時が経つとほころびを見つけてしまうものです。
先のエントリでは
異常処理のほころびを指摘されたので、JavaでI/Oをいじるさいの完璧な例外処理とはどのようなものか考えて見ました。
public void hoge() throws IOException {
boolean returnFlag = false;
boolean throwFlag = false;
OutputStream out = null;
try {
// ストリームのオープン
out = new FileOutputStream("test.txt"); // (A)
// 書き込み
out.write(0); // (B)
if (returnFlag) {
return; // (C)
}
if (throwFlag) {
throw new NullPointerException(); // (D)
}
// 正常系の後始末
out.close(); // (E)
out = null;
} finally {
if (out != null) {
// outが有効ならclose()を試みる
out.close(); // (ZA)
} else {
// outが無効なら何もしない
// (ZB)
}
}
}
まずは、上記のようなコードを書いてみました。通過ルートとしては
- 異常系 : A:IOException → ZB → A:IOException
- 異常系 : A → B:IOException → ZA → B:IOException
- 異常系 : A → B:IOException → ZA → ZA:IOException ※
- 正常系 : A → B → C → ZA → return
- 異常系 : A → B → C → ZA → ZA:IOException
- 異常系 : A → B → D:NullPointerException → ZA → D:NullPointerException
- 異常系 : A → B → D:NullPointerException → ZA → ZA:IOException ※
- 正常系 : A → B → E → ZB → return
- 異常系 : A → B → E:IOException → ZA → E:IOException
- 異常系 : A → B → E:IOException → ZA → ZA:IOException ※
が考えられます。
このソースでは、finallyでのclose()の際に発生したIOExceptionもメソッドからthrowされて
呼び元に通知する仕掛けなのですが、※で示した箇所では情報が欠損します。
本来の例外はきえてなくなり、close()で発生したIOExceptionに乗っ取られるのです。
そこでこれらの部分で例外のチェーンを用いて情報が欠損しないようにしてみたいと思います。
public void piyo() throws IOException {
boolean returnFlag = false;
boolean throwFlag = false;
OutputStream out = null;
Throwable throwable = null;
try {
// ストリームのオープン
out = new FileOutputStream("test.txt"); // (A)
// 書き込み
out.write(0); // (B)
if (returnFlag) {
return; // (C)
}
if (throwFlag) {
throw new NullPointerException(); // (D)
}
// 正常系の後始末
out.close(); // (E)
out = null;
} catch (IOException ioe) {
throwable = ioe; // (XA)
throw ioe;
} catch (RuntimeException e) {
throwable = e; // (XB)
throw e;
} finally {
if (out != null) {
// outが有効ならclose()を試みる
try {
out.close(); // (ZA)
} catch (IOException ioe) {
// finallyのclose()で例外が発生した場合、例外チェーンとする
if (throwable != null) {
ioe.initCause(throwable); // (ZA')
}
throw ioe; // (ZA'')
}
} else {
// outが無効なら何もしない
// (ZB)
}
}
}
これにより、通過ルートは以下のようになります。
- 異常系 : A:IOException → ZB → A:IOException
- 異常系 : A → B:IOException → XA → ZA → B:IOException
- 異常系 : A → B:IOException → XA →
ZA → ZA' → ZA'' → ZA:IOException(B:IOException)
- 正常系 : A → B → C → ZA → return
- 異常系 : A → B → C → ZA → ZA'' → ZA:IOException
- 異常系 : A → B → D:NullPointerException → XB → ZA → D:NullPointerException
- 異常系 : A → B → D:NullPointerException → XB →
ZA → ZA' → ZA'' → ZA:IOException(D:NullPointerException)
- 正常系 : A → B → E → ZB → return
- 異常系 : A → B → E:IOException → XA → ZA → E:IOException
- 異常系 : A → B → E:IOException → XA →
ZA → ZA' → ZA'' → ZA:IOException(E:IOException)
finally内ではtry-catch
ブロックで宣言された変数にアクセスできませんから、えらく面倒な作りになってしまいました。
ここまでしないと駄目なのでしょうか。言語のサポートも欲しいところですね。
投稿日時 : 2007年8月6日 18:04