墓場まで持っていく例外設計:VB.NETにおける「不沈艦」アプリケーションの構築術
VB.NETを扱うエンジニアにとって、アプリケーションが突如として「真っ白なダイアログ」と共に強制終了するのは、職務怠慢に等しい。特に現場のレガシーなWindows Formsアプリケーションでは、リソース管理の甘さがメモリリークを招き、それが例外の温床となる。
今日は、ユーザーにエラーを見せず、かつ開発者がデバッグに必要な情報を完璧に収集し、アプリを自律的に復旧させるための「例外制御の極致」を伝授する。
—
1. 例外の「二層構造」を理解せよ
VB.NETの例外処理には、明確な役割分担がある。多くの初心者はこれを混同し、ログを取り逃がす。
- `Application.ThreadException`: UIスレッド(メインスレッド)で発生した例外を捕捉する。WinFormsのイベントハンドラ内で投げられたエラーはここを通る。
- `AppDomain.CurrentDomain.UnhandledException`: スレッドプール内の非同期処理や、メインスレッド以外で発生した「致命的(Fatal)」な例外を捕捉する。
これら二つを適切にハンドリングしなければ、アプリケーションの「死角」を放置していることと同義だ。
—
2. 実装の核心:クラッシュレポート・ハンドラの構築
以下のコードは、単なるログ出力ではない。メモリの解放を意識し、OSの負荷を最小限に抑えつつ情報を収集する構成だ。
Imports System.Threading
Imports System.IO
Friend NotInheritable Class GlobalExceptionHandler
‘ アプリケーション開始時に一度だけ呼び出す
Public Shared Sub RegisterHandlers()
‘ UIスレッドの例外
AddHandler Application.ThreadException, AddressOf UIThreadExceptionHandler
‘ 非同期/バックグラウンドの例外
AddHandler AppDomain.CurrentDomain.UnhandledException, AddressOf UnhandledExceptionHandler
End Sub
Private Shared Sub UIThreadExceptionHandler(sender As Object, e As ThreadExceptionEventArgs)
ProcessException(e.Exception, “UIThread”)
End Sub
Private Shared Sub UnhandledExceptionHandler(sender As Object, e As UnhandledExceptionEventArgs)
ProcessException(DirectCast(e.ExceptionObject, Exception), “NonUIThread”)
End Sub
Private Shared Sub ProcessException(ex As Exception, source As String)
‘ 1. スタックトレースと環境情報を構築
Dim report As String = $”Source: {source}{vbCrLf}Trace: {ex.StackTrace}{vbCrLf}Message: {ex.Message}”
‘ 2. ここでサーバーへの送信処理(HTTP POSTなど)を実装する
‘ 注意: 同期的に送るとアプリがフリーズするため、タスクか別プロセスで送るのが鉄則
SendReportToServer(report)
‘ 3. メモリの安全な解放を促す(GCの強制収集は慎重に)
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced)
‘ 4. ユーザーへ通知し、必要なら再起動する
MessageBox.Show(“予期せぬエラーが発生しました。ログを送信しました。”, “System Error”, MessageBoxButtons.OK, MessageBoxIcon.Error)
Application.Restart()
Environment.Exit(0)
End Sub
End Class
—
3. シニアエンジニアが守るべき「3つの鉄則」
① メモリ管理は「明示的」に行え
VB.NETはガベージコレクション(GC)があるが、Windows FormsのCOMオブジェクトやグラフィックス・リソース(GDI+)は別物だ。`IDisposable`を実装しているオブジェクトは、例外発生時であっても確実に`Dispose()`を呼ぶ設計にしなければならない。`Using`構文を怠ることは、設計の怠慢である。
② コンテキストの保存
例外レポートには、以下の情報を必ず含めよ。これがないレポートはただのゴミである。
- OSのビルド番号: `Environment.OSVersion`
- メモリ使用量: `Process.GetCurrentProcess().PrivateMemorySize64`
- 実行モジュールのパス: アプリの更新状況を特定するため
③ 再起動の罠を回避せよ
`Application.Restart()`は強力だが、デッドロックやリソースの解放待ちが発生している場合、再起動自体が失敗する。必ずプロセス終了前に`Process.GetCurrentProcess().Kill()`に近い制御を検討すること。
—
4. 伝説的な現場の結論
多くの企業でVBAからVB.NETへの移行を指揮してきたが、結局のところ、優れたシステムとは「例外が起きないシステム」ではなく、「例外が起きた後の振る舞いが美しいシステム」のことだ。
エラーダイアログをユーザーに見せるのは敗北である。背後で静かに情報を吸い上げ、何事もなかったかのように再起動し、裏側で開発者が次のリリースまでに確実に修正できる体制を作ることこそが、エンジニアとしての真の矜持である。
この設計を導入した瞬間から、あなたのアプリケーションは「不安定なパッチワーク」から「堅牢なエンタープライズ製品」へと進化するはずだ。健闘を祈る。
