- エラハンドリングのベストプラクティスが知りたい!
このような疑問にお答えします。
Excel VBAって手軽にプログラムを組める一方で、エラーハンドリングをきちんと組んであるコードが少ない印象を受けています。
ただ、いざエラー処理を追加しようと思っても「一体どうやって組めば良いエラー処理ができるのか…」と途方に暮れてしまう方も多いと思います。
On Error GoTo
を使えば良い!といった記事は見つかりますが、次のような疑問への解決になっていないものがほとんどです。
- エラーの種類ごとに挙動を変える方法は?
- カスタムエラーを定義したいのだが…
- どんなエラーメッセージを設定したら良い?
本記事の内容が「答え」とまでは言いませんが、僕が普段VBAコードを組む際の考え方をお伝えできたらと思います。
エラーハンドリングについて漠然とした不安がある方は、ぜひ参考にしてください。
エラーハンドリングのベストプラクティス
質の高いエラーハンドリングを行うまでの流れは以下の通りです。
- カスタムエラーの定義
- エラーを発生させる
- エラー処理を構造化
- ログ記録をする
- 上位モジュールに伝播させる
- クリーンアップ処理
- ユーザーにエラー内容を伝える
一つ一つは大して難しくないので、順を追って説明していきます。
カスタムエラーの定義
VBAではデフォルトでもエラーが定義されていますが、それ以外にカスタムでエラーを定義させたい場合があります。
そのような時にはシステム定義のエラー番号(1~65535)との衝突を避けるために、独自の範囲を設定しましょう。
一般的には100000以上の数値を指定することが多いです。
さらに付け加えると、エラーのカテゴリごとに1000区切りなどで番号を振ると管理が楽になります。
' データベース関連のエラー
Public Const ERR_DB_CONNECTION_FAILED As Long = 100000
Public Const ERR_DB_QUERY_FAILED As Long = 100001
' ファイル操作関連のエラー
Public Const ERR_FILE_NOT_FOUND As Long = 101000
Public Const ERR_FILE_READ_FAILED As Long = 101001
' ユーザー認証関連のエラー
Public Const ERR_USER_NOT_AUTHORIZED As Long = 102000
Public Const ERR_USER_NOT_FOUND As Long = 102001
' ネットワーク関連のエラー
Public Const ERR_NETWORK_TIMEOUT As Long = 103000
Public Const ERR_NETWORK_UNREACHABLE As Long = 103001
エラーを発生させる
Err.Raise
メソッドを使ってカスタムエラーを発生させます。
Err.Raise ERR_DB_CONNECTION_FAILED, "ConnectDatabase", "データベースへの接続に失敗しました。"
引数の内容は以下の通りです。
引数 | 引数の内容 |
---|---|
第1引数 | エラー番号 |
第2引数 | エラーの発生源 |
第3引数 | エラーの説明文 |
Err
オブジェクトの内容にあたる部分を作っていく工程になります。
エラー処理を構造化
Err.Raise
で発生させたエラーを補足するためのコードを追加します。
やることは二段階あります。
On Error GoTo
でエラーハンドラーを設定Select Case
でエラーごとに処理を変える
具体的なコード例を挙げます。
Sub ConnectDatabase()
On Error GoTo ErrorHandler
' データベースエラーを発生させる
Err.Raise ERR_DB_CONNECTION_FAILED, "ConnectDatabase", "データベースへの接続に失敗しました。"
Exit Sub
ErrorHandler:
Select Case Err.Number
Case ERR_DB_CONNECTION_FAILED
MsgBox "ERR_DB_CONNECTION_FAILED: " & Err.Description
Case ERR_DB_QUERY_FAILED
MsgBox "ERR_DB_QUERY_FAILED: " & Err.Description
Case Else
MsgBox "予期せぬエラー: " & Err.Description
End Select
Err.Clear
End Sub
上記のCase Else
ではDescription
を定義していないのでは?と思われるかもしれませんが、問題なしです。
というのもCase Else
に入る場合はシステムエラーの場合なので、システム側で用意されたエラーメッセージが自動的に入るためです。
もちろん、カスタムエラーならErr.Raiseの第二引数で詳細を指定しているので、こちらも問題なしですね。
ログ記録をする
エラーの詳細情報を、ログファイルやデータベースに記録します。
Sub LogError(errorNumber As Long, errorDescription As String)
' ログファイルにエラー情報を書き込む
' または、データベースに記録する
End Sub
ログファイルの作成方法は、別記事でご紹介する予定です。
上位モジュールに伝播させる
呼び出し元のモジュールがある場合には、必要に応じてErr.Raise
で上位モジュールにエラー内容を伝播させます。
If Err.Number = ERR_DB_CONNECTION_FAILED Then
' エラー処理
Err.Raise Err.Number, Err.Source, Err.Description
End If
この場合には、下位モジュールではエラーを上位に伝播させるだけでCase
文などは使いません。
そして伝播されたエラーは、上位モジュール側で処理をすることになります。
Sub UpperLevelFunction()
On Error GoTo ErrorHandler
' この関数でエラー発生
Call LowerLevelFunction()
' 正常終了時の処理
MsgBox "処理が正常に完了しました"
Exit Sub
ErrorHandler:
' 上位モジュールでのエラー処理
Select Case Err.Number
Case ERR_DB_CONNECTION_FAILED
MsgBox "DB接続エラーが発生しました: " & Err.Description
Case ERR_DB_QUERY_FAILED
MsgBox "DBクエリエラーが発生しました: " & Err.Description
Case Else
MsgBox "予期せぬエラーが発生しました: " & Err.Description & " (エラー番号: " & Err.Number & ")"
End Select
' エラー情報をクリア
Err.Clear
End Sub
もちろん、下位モジュールでエラー処理をして上位モジュールにはエラーを伝播させないことも可能です。
Sub LowerLevelFunction()
On Error GoTo ErrorHandler
' 処理
Err.Raise 1001, "LowerLevelFunction", "下位モジュールでのエラー"
Exit Sub
ErrorHandler:
MsgBox "エラー: " & Err.Description
Err.Clear
Exit Sub
End Sub
Err.Clear
することで、上位モジュールにエラーが伝播しません。
クリーンアップ処理
プロシージャ内で使われたリソースの解放なども、忘れずに行うようにします。
例えば以下のようにExitSub
ラベルをfinally
的な用途で使うことで、サブプロシージャの終了時に必ず実行したい処理を指定できます。
Sub ConnectDatabase()
On Error GoTo ErrorHandler
' 正常終了時の処理
Dim conn As ADODB.Connection
Set conn = New ADODB.Connection
conn.Open "connection string"
GoTo ExitSub
ErrorHandler:
' エラー処理
Resume ExitSub
ExitSub:
If Not conn Is Nothing Then
If conn.State = adStateOpen Then conn.Close
Set conn = Nothing
End If
Exit Sub
ユーザーにエラー内容を伝える
ユーザーにはわかりやすいメッセージを表示します。
MsgBox "データの保存中にエラーが発生しました。ネットワーク接続を確認してください。", vbExclamation, "エラー"
実装側だとどうしても専門的な言葉になってしまいがちですが、利用者の立場に立って平易な言葉で内容を伝えるようにしましょう。
まとめ
エラーハンドリングは型を身につけてしまえば、そこまで恐れることもないかと思っています。
まずは本記事の内容を実践していただき、合わない部分を改善しつつ自分なりのベストプラクティスを作り上げていただけたらと思います。
本ブログでは、他にもVBAに関する記事を公開しているので合わせてご覧ください!
コメント