スタックトレースを殺すな:VB.NETで例外を「正確に」再スローする極意
業務自動化ツールを開発していると、必ず直面する壁がある。それは「非同期処理で発生したエラーが、どこで起きたのか分からない」という問題だ。
多くのエンジニアは、`Try-Catch`で捕まえた例外をそのまま `Throw ex` して済ませる。しかし、これこそがデバッグ地獄への片道切符だ。`Throw ex` を実行した瞬間、元のスタックトレースは破壊され、エラーの発生源は「再スローした場所」に上書きされてしまう。
今日は、`.NET`の深淵に触れる技術、`System.Runtime.ExceptionServices.ExceptionDispatchInfo` を駆使し、別スレッドの例外をメインスレッドで「当時のまま」再現する方法を授けよう。
—
1. なぜ「Throw ex」が最悪の選択肢なのか
初心者がやりがちな以下のコードを見てほしい。
Try
‘ データベース接続処理など
Catch ex As Exception
‘ ここでThrow exをすると…
Throw ex ‘ 致命的な設計ミス!
End Try
これを行うと、スタックトレースの起点が「再スローした行」に置き換わる。数千行ある業務コードの中で、真の発生源(例:DBの接続文字列不正なのか、SQLの文法エラーなのか)を特定するコストは跳ね上がる。ログを見ても犯人が分からないコードは、プロダクションコードではない。
—
2. ExceptionDispatchInfo:例外の完全保存と転送
`ExceptionDispatchInfo` は、例外オブジェクトと当時のスタックトレース情報を「カプセル化」して保持するクラスだ。これを使えば、例外をキャプチャし、好きなタイミングで、まるでその場所で発生したかのように再スローできる。
実践的コード:スレッド間エラー伝播モデル
以下は、バックグラウンドタスク(DB処理やファイルI/O)で発生したエラーをメインスレッドに安全に持ち帰るためのテンプレートだ。
Imports System.Runtime.ExceptionServices
Imports System.Threading.Tasks
Public Class ExceptionHandlerService
”’
”’
Public Sub ExecuteSecureTask()
Dim capturedException As ExceptionDispatchInfo = Nothing
‘ 別タスクでの処理をシミュレート
Task.Run(Sub()
Try
‘ ここでDB操作やファイル読み込みを行う
Throw New InvalidOperationException(“DB接続がタイムアウトしました。”)
Catch ex As Exception
‘ 例外情報をスタックトレースと共に保存
capturedException = ExceptionDispatchInfo.Capture(ex)
End Try
End Sub).Wait()
‘ メインスレッド側でキャプチャ済み例外を再スロー
If capturedException IsNot Nothing Then
‘ ここで投げると、あたかもTask内で起きた例外として再スローされる
capturedException.Throw()
End If
End Sub
End Class
—
3. なぜこの設計が「堅牢」なのか
このアプローチには、業務自動化エンジニアとして押さえておくべき「3つの優位性」がある。
1. スタックトレースの保持: `Throw` した場所ではなく、実際に例外が発生した行番号まで正確にデバッガが追跡できる。
2. スレッド境界の超越: `Task` や `Thread` を跨いでも例外の「文脈」が失われない。UIスレッドへの通知やログ出力において、極めて強力な武器となる。
3. 保守性の向上: エラーハンドリングのロジックを共通化できる。`Capture` する場所と `Throw` する場所を分離することで、エラーログのフォーマットを一元管理できるからだ。
—
4. 業務自動化における注意点
この技術は強力だが、銀の弾丸ではない。以下の運用ルールを徹底すること。
- `Finally` ブロックとの併用: ファイルやDBのコネクションを確実に閉じるため、例外キャプチャ前後には必ず `Finally` ブロックでリソース解放を行え。`Using` ステートメントの使用は鉄則だ。
- ログ出力は「一度だけ」: キャプチャした例外を再スローする前には、必ずログ出力(NLogやLog4Net等)を行え。再スローした先で何度もログを吐くと、ログファイルがノイズで埋め尽くされる。
- 例外の握りつぶし厳禁: `Catch` 句で何も書かないコード(空のCatch)は、システムを「ゾンビ化」させる。最低限、`ExceptionDispatchInfo` でのキャプチャと再スローを義務付けろ。
—
結論
Visual Basicという言語は、古臭いのではない。使い方次第で、これほどまでに洗練されたエラーハンドリングを実現できる現代的なツールだ。
「エラーを捕まえて、投げる」という単純な動作一つとっても、そこに込められた設計意図がプロダクトの寿命を決める。次に君が書くコードでは、ぜひこの `ExceptionDispatchInfo` を使い、完璧なトレーサビリティを備えた自動化ツールを実現してほしい。
現場の混乱を未然に防ぐことこそ、真のエンジニアリングである。
