【実務・中級編】実務中級者向け:VB.NETでのカスタム例外の作成とスロー:業務エラーコードや追加情報を保持する独自の例外設計の極意 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

【VB.NET極意】業務エラーを制する者がシステムを制す:カスタム例外設計の深淵

開発現場でよく見かける光景がある。すべての例外を`Catch ex As Exception`で一網打尽にし、`MessageBox.Show(ex.Message)`で画面に垂れ流すコード。そして、ログには「エラーが発生しました」という無機質な文字列だけが残る。

これでは、実務に耐えうる堅牢な業務アプリケーションとは言えない。

システムが予期せぬクラッシュを起こした「システムエラー(バグ・通信断・DB障害)」と、ユーザーの入力不備や残高不足といった「業務上のルール違反(業務エラー)」は、明確に分離されなければならない。

今回は、VB.NET中級者から一歩抜け出し、大規模な業務システムや自動化ツールを支える「カスタム例外(Custom Exception)」の設計と実装の極意を伝授しよう。

1. なぜカスタム例外が必要なのか?

標準の`Exception`クラスだけでは、業務ロジックの意図を上位レイヤー(UI層やログ出力層)へ正確に伝達できない。

例えば、「口座残高不足」という事象を考えてみる。
これを単なる `Exception` や `ApplicationException`(※現在は非推奨)として投げると、呼び出し元ではメッセージの文字列を「部分一致」で判定するような脆弱なコードを書く羽目になる。

‘ 【アンチパターン】文字列依存の判定。メッセージ仕様変更で即死する。
If ex.Message.Contains(“残高が不足”) Then
‘ 残高不足のリカバリ処理
End If

カスタム例外を導入すれば、「型」による厳密なハンドリングが可能になる。さらに、エラーコードや追加情報(不足金額や対象口座IDなど)をプロパティとして型安全に保持できるため、保守性が劇的に向上する。

2. 堅牢なカスタム例外クラスの設計原則

.NETの世界でカスタム例外を作る際、守るべき鉄則が3つある。

1. `Exception` クラスを継承する(またはその派生)。
2. クラス名の末尾は必ず `Exception` で終わらせる(コーディング規約の絶対遵守)。
3. シリアライズを考慮したコンストラクタを完備する(特にCOM InteropやWCF、remoting等を意識する場合)。

実務でそのまま使える、業務エラー用カスタム例外の決定版コードを見てほしい。

3. 【プロダクションコード】業務エラー例外の実装

以下のコードは、エラーコード (`ErrorCode`) と、業務的な追加データ (`ErrorContext`) を保持できる汎用的な業務例外クラスの模範解答だ。

Imports System
Imports System.Runtime.Serialization

Namespace Business.Exceptions

”’

”’ 業務ロジック上の違反を表すベースカスタム例外クラス
”’


Public Class BusinessLogicException
Inherits Exception

‘ 業務エラーコード(例: “ERR_FIN_001″)
Public ReadOnly Property ErrorCode As String

‘ 追加のコンテキスト情報(必要に応じて画面表示やログに活用)
Public ReadOnly Property ErrorContext As Dictionary(Of String, String)

‘ デフォルトコンストラクタ
Public Sub New()
MyBase.New(“業務処理実行中にエラーが発生しました。”)
Me.ErrorCode = “ERR_BIZ_GENERAL”
Me.ErrorContext = New Dictionary(Of String, String)()
End Sub

‘ メッセージを受け取るコンストラクタ
Public Sub New(message As String)
MyBase.New(message)
Me.ErrorCode = “ERR_BIZ_GENERAL”
Me.ErrorContext = New Dictionary(Of String, String)()
End Sub

‘ メッセージとエラーコードを受け取るコンストラクタ(実務で最も多用)
Public Sub New(message As String, errorCode As String)
MyBase.New(message)
Me.ErrorCode = errorCode
Me.ErrorContext = New Dictionary(Of String, String)()
End Sub

‘ メッセージ、エラーコード、内部例外を受け取るコンストラクタ
Public Sub New(message As String, errorCode As String, innerException As Exception)
MyBase.New(message, innerException)
Me.ErrorCode = errorCode
Me.ErrorContext = New Dictionary(Of String, String)()
End Sub

‘ 構造化データ(コンテキスト)を付与できるコンストラクタ
Public Sub New(message As String, errorCode As String, context As Dictionary(Of String, String))
MyBase.New(message)
Me.ErrorCode = errorCode
Me.ErrorContext = If(context, New Dictionary(Of String, String)())
End Sub

‘ シリアライズ用コンストラクタ(.NETの例外設計ガイドライン必須要件)
Protected Sub New(info As SerializationInfo, context As StreamingContext)
MyBase.New(info, context)
Me.ErrorCode = info.GetString(“ErrorCode”)

‘ 簡易的なディクショナリ復元(実運用では型安全なデシリアライズを推奨)
‘ ここでは簡略化のため省略
End Sub

‘ シリアライズ処理のオーバーライド

Public Overrides Sub GetObjectData(info As SerializationInfo, context As StreamingContext)
MyBase.GetObjectData(info, context)
info.AddValue(“ErrorCode”, Me.ErrorCode)
End Sub

End Class

End Namespace

4. 実戦投入:サービス層でのスローとUI層でのキャッチ

では、このカスタム例外を実際の業務ロジック(例:銀行口座の出金処理)でどう使うか。アーキテクチャの上下関係を意識した実装例を示す。

サービス層(ビジネスロジック)でのスロー

Public Class AccountService

Public Sub Withdraw(accountId As String, amount As Decimal, currentBalance As Decimal)

‘ 残高不足のチェック
If currentBalance < amount Then ' 追加情報をディクショナリで持たせることで、上位層でのリッチなハンドリングが可能に Dim extraInfo As New Dictionary(Of String, String) From { {"AccountId", accountId}, {"Required", amount.ToString("C")}, {"Balance", currentBalance.ToString("C")} } ' カスタム例外をスロー Throw New BusinessLogicException( "口座残高が不足しているため、出金処理を完了できません。", "ERR_FIN_INSUFFICIENT_FUNDS", extraInfo ) End If ' 通常の出金処理... End Sub End Class

UI層(またはエントリポイント)でのスマートなハンドリング

Public Class MainForm

Private Sub btnWithdraw_Click(sender As Object, e As EventArgs) Handles btnWithdraw.Click
Dim service As New AccountService()

Try
‘ 業務処理の実行
service.Withdraw(“ACC-1005″, 50000D, 10000D)

Catch ex As BusinessLogicException
‘ 【業務エラーのキャッチ】
‘ ユーザーに対しては優しく、かつ正確なコードと共に通知
Dim detailMsg As String = $”[エラーコード: {ex.ErrorCode}]” & vbCrLf & ex.Message

‘ コンテキスト情報の活用(例:不足額を表示するなど)
If ex.ErrorContext.ContainsKey(“Required”) Then
detailMsg &= vbCrLf & $”不足金額: {ex.ErrorContext(“Required”)}”
End If

MessageBox.Show(detailMsg, “業務エラー”, MessageBoxButtons.OK, MessageBoxIcon.Warning)

‘ 業務ログにはコードと詳細を出力
Logger.Warn($”業務エラー発生: {ex.ErrorCode} – {ex.Message}”)

Catch ex As Exception
‘ 【システムエラー(予期せぬバグ等)のキャッチ】
Logger.Error(“予期せぬシステムエラーが発生しました。”, ex)
MessageBox.Show(“システム内部でエラーが発生しました。管理者に連絡してください。”, “致命的エラー”, MessageBoxButtons.OK, MessageBoxIcon.Error)

End Try
End Sub

End Class

5. プロジェクトアーキテクトからの提言

カスタム例外を導入する最大のメリットは、「エラーハンドリングの責任を適切なレイヤーに割り振れること」にある。

  • UI層は、例外の「型」を見て、メッセージボックスの色を変えたり(業務エラーなら黄色の警告、システムエラーなら赤の致命的)、リトライボタンを表示したりする制御に専念できる。
  • データアクセス層(DB・ファイル連携)は、接続切れや一意制約違反などを低レベルの例外として捉え、必要に応じて `BusinessLogicException` にラップ(包み直してスロー)して上位に伝えることで、DBの実装詳細を上位に隠蔽できる。

「ただ動くコード」から「保守しやすく、拡張性のあるコード」へ。
今日のこの設計をあなたのプロジェクトに組み込めば、エラーログの解析スピードとアプリケーションの信頼性は劇的に跳ね上がるはずだ。

タイトルとURLをコピーしました