掲示板お問い合わせランダムジャンプ



この広告は30日以上更新がないブログに表示されております。 新しい記事を書くことで広告を消すことができます。

Top Index

2014年07月27日
例外処理入門(その2)
例外処理入門(その1)の続き。

例外の再発生(その1:ThrowとThrow exの違い)


「例外発生時の事後処理を書き、エラー処理は呼び出し元に任せる」ということはよくあります。
エラーのメッセージ処理がそうですね。
例を挙げると、こんな感じ。

※例外処理入門その1で「Exceptionクラスを直接発生させたり、捕まえたりするな」と言っておりましたが、
 毎回あれを考慮したコードを載せていては情報量が不必要に多くなるので敢えてこのように書いております。
 ご了承ください。

ここで23行目に注目!
例外を再発生させるコードは、
Throw
です。
たまに
Throw ex
と記述する人がいます。
たぶん、例外の任意発生(Throw New Exception)の構文からこう書いてしまったのでしょうが、
Throw とだけ書くようにしてください。

強調するのにはわけがありまして、「Throw だけ」と「Throw ex」ではエラーが発生した位置情報(StackTrace)が異なってくるからなのです。
以下にStackTraceの実例を挙げます。

Throw ex と書いた場合のメッセージとStackTrace


ファイル 'c:\data.txt' が見つかりませんでした。
場所 ConsoleApplication1.Module2.ExecuteHoge() 場所 C:\...\Module2.vb:行 23
場所 ConsoleApplication1.Module2.Main() 場所 C:\...\Module2.vb:行 6


エラー発生場所が「23行目」となっていますが、23行目は
Throw ex
ですので、こんなところでエラー起きてますといわれても「絶対そこじゃない」というわけです。

Throw と書いた場合のメッセージとStackTrace


ファイル 'c:\data.txt' が見つかりませんでした。
場所 System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
場所 System.IO.File.InternalCopy(String sourceFileName, String destFileName, Boolean overwrite, Boolean checkHost)
場所 System.IO.File.Copy(String sourceFileName, String destFileName)
場所 ConsoleApplication1.Module2.ExecuteHoge() 場所 C:\...\Module2.vb:行 23
場所 ConsoleApplication1.Module2.Main() 場所 C:\...\Module2.vb:行 6


行数は「23行目」と同じですが、それより前の情報が取得できます。
行数はここまでしか特定できませんが、「System.IO.File.Copy」メソッドでこけたことが推察できます。


例外の再発生(その2:内部例外)


Throwと書く以外にも例外を再発生方法があります。
内部例外です。
ExceptionクラスにはInnerExceptionプロパティというものがあり、これを使用します。

ポイントは29行目のコードです。
Throw New Exception("Hogeに失敗しました", ex)
第2引数が内部例外(InnerException)に代入されます。

 exのメッセージとStackTrace


Hogeに失敗しました
場所 ConsoleApplication1.Module2.ExecuteHoge()
場所 C:\...\Module2.vb:行 23
場所 ConsoleApplication1.Module2.Main() 場所 C:\...\Module2.vb:行 6


 ex.InnerExceptionのメッセージとStackTrace


ファイル 'c:\data.txt' が見つかりませんでした。"
場所 System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
場所 System.IO.File.InternalCopy(String sourceFileName, String destFileName, Boolean overwrite, Boolean checkHost)
場所 System.IO.File.Copy(String sourceFileName, String destFileName)
場所 ConsoleApplication1.Module2.ExecuteHoge()
場所 C:\...\Module2.vb:行 15


この方法であればInnerExceptionプロパティをみることで、15行目が原因であることまでトレースできます。
ただ、例外をラッピングしないといけないため、エラーメッセージがわかりづらくなりがちです。
ここら辺は「例外の再発生(その1)、(その2)どっちにすべき」と縦割りすることはできませんので、
用途に応じて使い分けてください。

エラーメッセージの取得


ex.Message
と記述すればいいと思うかもしれませんが、実際には誤り(というか不十分)です。
先程挙げた内部例外があるかどうかを見て、内部例外まで評価しなけば正しい情報を取得できません。

こちらは機械的に内部例がNothingじゃなければ再帰して取得すればいいだけの話ですので、
拡張メソッドのコードを置いておきます。



Catchブロックに「Throw」の1行だけ書くのはNG


例えば、「例外処理はないけど、事後処理だけはしたい」場合、こういうコードを書く人がいます。


文法上間違いではないですが、

例外を再発生すると、StackTraceが変わる。
Catchブロックは必須ではない。

ので、
Catchブロックが「Throw」の1行しか書いてないのであれば、そもそもCatchする必要がありません。
以下のようにしましょう。


コード量も減らせられるし、StackTraceもきれいに出ます。
最後に修正前と修正後のStackTraceを比較してみましょう。

修正前のメッセージとStackTrace


ファイル名 'c:\data.txt' です。'c:\data.txt'
場所 System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
場所 System.IO.File.InternalCopy(String sourceFileName, String destFileName, Boolean overwrite, Boolean checkHost)
場所 System.IO.File.Copy(String sourceFileName, String destFileName)
場所 ConsoleApplication1.Module3.ExecuteHoge() 場所 C:\...\\Module3.vb:行 19
場所 ConsoleApplication1.Module3.Main() 場所 C:\...\\Module3.vb:行 6


修正後のメッセージとStackTrace


ファイル 'c:\data.txt' が見つかりませんでした。
場所 System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
場所 System.IO.File.InternalCopy(String sourceFileName, String destFileName, Boolean overwrite, Boolean checkHost)
場所 System.IO.File.Copy(String sourceFileName, String destFileName)
場所 ConsoleApplication1.Module3.ExecuteHoge() 場所 C:\...\\Module3.vb:行 15
場所 ConsoleApplication1.Module3.Main() 場所 C:\...\\Module3.vb:行 6


修正後の方がわかりやすいですね。


例外処理が不要なら、そもそも書く必要はない


例外処理を覚えると、今度はやたらと組み込み始める人がいます。
極端な例だとすべてのサブルーチンに組み込み始めるのですが、処理がないなら書く必要はありません。
既に例を挙げてしますが、
サブルーチンで例外が発生しても、呼び出し元にTry〜Catch文が書かれていれば、呼び出し元のCatchブロックまでぶっ飛びます。

※サブルーチンでエラーが出てもちゃんとメッセージ処理はされます。

一般的には(メッセージ処理を行う関係上)呼び出し元のルートにはTry〜Catch文を書く必要はありますが、
それ以外は例外処理、事後処理が存在しないならTry〜Catch文を書く必要はありません。


積極的に例外を発生させよう(任意)カスタム例外を使ったエラーメッセージ処理


処理結果を戻り値で判定するコードはよく見ます。
例を挙げるとこんな感じ。

※戻り値をBoolean型でやっていますが、String型(エラーメッセージ)を返すパターンもあります。

「処理失敗なら必ずエラー処理をして抜ける」という前提であれば、以下の点でよろしくありません。
・isSuccess(実行結果を管理する変数)への代入忘れに気を付ける必要があり、成功失敗判定が狂いやすい。
・例外のメッセージ処理を都度実装しており、冗長。

改善方法はいろいろありますが、「異常が見つかったらカスタム例外で返す」のがよいでしょう。
修正後のコードはこちら。

※今回は「FileNotFoundException」を使用しましたが、カスタム例外で読み替えてください。

以下の点が改善されました。
・サブルーチンは失敗した場合、失敗の理由(data.txtがファイルがありません。)がわかるようになった。
・サブルーチンは戻り値管理が不要になった。
・メインルーチンは成否判定が不要になった。
・メインルーチンの例外メッセージ処理がTry〜Catch文1つに集約された。


備考:
今回は「処理失敗なら必ずエラー処理をして抜ける」前提でのコードでの話です。
[ 投稿者:mk3008 at 10:57 | VB.NET | コメント(3) | トラックバック(0) ]