\ お問い合わせはこちら! /

【Excel VBA】エラーハンドリングのベストプラクティス【マクロ】

  • エラハンドリングのベストプラクティスが知りたい!

このような疑問にお答えします。

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に関する記事を公開しているので合わせてご覧ください!

» 参考:CFXLOG ExcelVBAの関連記事一覧を見てみる

この記事が気に入ったら
フォローしてね!

シェア・記事の保存はこちら!

この記事を書いた人

karo@プログラマのアバター karo@プログラマ プログラマ

「書くことで人の役にたつ」をモットーに活動中。
本職はプログラマで、Python(Django)が得意。最近ではフロント側の技術に触れる機会も増えてきました。
基本情報技術者試験合格。

コメント

コメントする

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)