VBA業務マクロの業務ロジックはエラーを戻り値で返す

VBA業務マクロの業務ロジックはエラーを戻り値で返す

Err.Raise、Boolean、配列、ByRef の役割を分け、BIZ_PROC_OK / BIZ_PROC_NG の標準形を決める

Copyright © 2026 LWP 山中 一弘

本資料は、出典を明記いただければ、商用・非商用を問わず、ご自由に複製・改変・再配布していただけます。なお、著作権表示は改変せず、そのまま記載してご利用くださいますようお願いいたします。

記事要約

Excel VBAの業務マクロでは、業務ロジック関数から上位層へ「処理が成功したか」と「失敗した理由」を返す場面が多くあります。ここで毎回 MsgBox を出したり、Err.Raise に寄せたり、Boolean と数値ステータスを混ぜたりすると、呼び出し側の読み方が不安定になります。

この記事では、業務ロジックからエラー状態を返す方法を、配列、戻り値と ByRef、すべて ByRef、Boolean、グローバル変数、例外、CVErr、クラスやユーザー定義型に分けて整理します。そのうえで、滝Libを使ったExcel VBA業務マクロでは、戻り値に BIZ_PROC_OK / BIZ_PROC_NG を返し、具体的なエラーメッセージを ByRef a_err_msg As String で返す形を標準にします。

結論は単純です。業務ロジックの成否は戻り値で返し、ユーザーに伝える具体的な内容はメッセージとして返します。業務上ありえるNGは通常の分岐で扱い、想定外の内部不整合は Err.Raise で止め、セルや配列内のエラー表現は CVErr に任せます。この分担を決めると、業務マクロのエラー処理は、場当たり的な後始末ではなく、読み手が追える設計になります。

本記事の対象とゴール

想定読者

  • Excel VBAで業務マクロを作っている人

  • 業務チェック関数の戻り値を Boolean にするか、数値ステータスにするか迷っている人

  • 下位関数の中で MsgBox を出す設計に違和感がある人

  • Err.Raise、戻り値、ByRef、CVErr の使い分けを整理したい人

  • 滝Libの BIZ_PROC_OK / BIZ_PROC_NG を業務ロジックの標準形として使いたい人

本記事で得られること

  1. 業務ロジック関数から返すべき情報を、処理ステータスとエラーメッセージに分けて考えられます。

  2. BIZ_PROC_OK / BIZ_PROC_NG と ByRef a_err_msg を使った標準テンプレートを持てます。

  3. Boolean、配列返却、グローバル変数、例外、CVErr、結果オブジェクトを、用途別に使い分けられます。

  4. 業務上ありえるNGと、プログラム上の内部不整合を分けて扱えます。

  5. UI層、業務ロジック層、共通関数・ライブラリ層の責務を混ぜずに設計できます。

1. 業務ロジックから返したい情報は二つある

業務マクロの業務ロジック関数から上位層へ返したい情報は、大きく二つあります。

  1. 処理が正常終了したか、エラー終了したか

  2. エラー終了した場合、その具体的な内容は何か

たとえば、元データフォルダをチェックする関数を考えます。

Call 元データチェック()

この関数の中では、次のようなことを確認します。

  1. 元データフォルダが存在するか

  2. フォルダ内にファイルがあるか

  3. ファイルが複数入っていないか

  4. 対象ファイルに必要なシートがあるか

  5. シートの見出しに必要項目があるか

このとき、呼び出し側が知りたいことは、単に「成功したか」だけではありません。

失敗したこと
なぜ失敗したのか
どこを直せばよいのか

この三つが分からないと、ユーザーに案内できません。

したがって、業務ロジック関数は、少なくとも次の二つを返す必要があります。

情報 例
処理ステータス BIZ_PROC_OK、BIZ_PROC_NG
エラーメッセージ 元データフォルダにファイルがありません。

問題は、この二つをどの形で返すかです。

2. レイヤーで返し方を分ける

業務マクロのエラー処理は、レイヤーで分けて考えると整理しやすくなります。

レイヤー 役割 エラーの返し方
UI層 ボタン、MsgBox、画面表示 メッセージを表示する
業務ロジック層 業務ルール、チェック、変換、転記判断 BIZ_PROC_OK / BIZ_PROC_NG とメッセージを返す
共通関数・ライブラリ層 汎用処理、型変換、配列処理、ファイル処理 必要に応じて Err.Raise、CVErr、戻り値を使う

ここで重要なのは、業務ロジック層はUI層ではない、ということです。

業務ロジック層は、「何が起きたか」を判断する場所です。ユーザーにどう見せるかは、原則としてUI層が決めます。

したがって、業務ロジック関数の中で毎回 MsgBox を出す設計は、基本形としては避けます。

' 原則として避けたい
If xcount = 0 Then
    MsgBox "元データがありません。"
    Exit Function
End If

代わりに、メッセージを返します。

If xcount = 0 Then
    a_err_msg = "元データがありません。"
    Exit Function
End If

表示は上位層で行います。

If biz元データチェック(xpath, xmsg) = BIZ_PROC_NG Then
    MsgBox xmsg, vbExclamation
    Exit Sub
End If

この分離ができると、テスト、再利用、修正がしやすくなります。

3. エラー状態を返す方式を比較する

エラー状態とエラーメッセージを返す方法には、いくつかの候補があります。

No 方式 例
1 配列で返す Array(BIZ_PROC_NG, errno, msg)
2 戻り値でステータス、ByRef でメッセージ Function ... (ByRef a_err_msg) As Long
3 ステータスもメッセージも ByRef Sub ... (ByRef a_status, ByRef a_err_msg)
4 Boolean でエラー有無を返す Function hasError(...) As Boolean
5 グローバル変数に最後のエラーを入れる BIZ_LAST_ERROR_MESSAGE
6 例外で返す Err.Raise
7 Excelエラー値で返す CVErr(xlErrNA)
8 クラスやユーザー定義型で返す BizResult.Status、BizResult.Message

どれも考え方としては成立します。問題は、今回の対象である「滝Libを使ったExcel VBA業務マクロ」の標準形として、どれが一番事故が少なく、説明しやすく、保守しやすいかです。

4. 配列で返す

一つ目は、処理結果を配列で返す方式です。

Public Function biz元データチェック(ByVal a_folder_path As String)
    
    If a_folder_path = vbNullString Then
        biz元データチェック = Array(BIZ_PROC_NG, 3001, "元データフォルダが指定されていません。")
        Exit Function
    End If
    
    biz元データチェック = Array(BIZ_PROC_OK, 0, vbNullString)
End Function

呼び出し側は次のようになります。

Dim xresult
xresult = biz元データチェック(xpath)
If xresult(0) = BIZ_PROC_NG Then
    MsgBox xresult(2), vbExclamation
    Exit Sub
End If

この方式では、一つの戻り値の中に、ステータス、エラー番号、メッセージをまとめて入れられます。

滝Libにも、この考え方に近いヘルパーがあります。

bizNGMake = Array(BIZ_PROC_NG, a_errno, a_desc)
bizOKMake = Array(BIZ_PROC_OK, a_errno, a_desc)

良いところ

良いところ 内容
戻り値にまとまる ステータス、エラー番号、メッセージを一つの値として返せる
入力引数と出力情報が分かれる ByRef を使わなくても結果を返せる
汎用化しやすい ログや共通処理でまとめて扱いやすい
滝Lib内のヘルパーと相性がある bizNGMake、bizOKMake の考え方に近い

悪いところ

悪いところ 内容
添字の意味が見えない xresult(0)、xresult(1)、xresult(2) の意味を覚える必要がある
初級者に分かりにくい 配列の中身が暗黙の約束になりやすい
型が弱い VBAでは Variant になりやすい
添字ミスに弱い xresult(1) と xresult(2) を間違えてもコンパイルでは分かりにくい
関数の意図が読み取りにくい 呼び出し側だけ見ると、何を返しているのか見えにくい

配列方式を使うなら、少なくとも添字に名前を付けます。

Public Const BIZ_RESULT_STATUS = 0
Public Const BIZ_RESULT_ERRNO = 1
Public Const BIZ_RESULT_MSG = 2
If xresult(BIZ_RESULT_STATUS) = BIZ_PROC_NG Then
    MsgBox xresult(BIZ_RESULT_MSG), vbExclamation
End If

これで多少読みやすくなります。ただし、業務マクロの標準形としては、まだ少し抽象度が高い方式です。

配列返却は、滝Lib内部、汎用ヘルパー、テスト用の結果オブジェクト、ログ収集などでは使えます。しかし、通常の業務ロジック関数の標準形にするには、読み手を選びます。

分かっている人には便利ですが、慣れていない人には見通しが悪い方式です。

5. 戻り値でステータスを返し、ByRefでメッセージを返す

二つ目は、関数の戻り値で処理ステータスを返し、エラーメッセージは ByRef 引数で返す方式です。

Public Function biz元データチェック( _
    ByVal a_folder_path As String, _
    ByRef a_err_msg As String _
) As Long
    
    biz元データチェック = BIZ_PROC_NG
    a_err_msg = vbNullString
    
    If a_folder_path = vbNullString Then
        a_err_msg = "元データフォルダが指定されていません。"
        Exit Function
    End If
    
    If Dir(a_folder_path, vbDirectory) = vbNullString Then
        a_err_msg = "元データフォルダが存在しません。" & vbCrLf & a_folder_path
        Exit Function
    End If
    
    biz元データチェック = BIZ_PROC_OK
End Function

呼び出し側は次のようになります。

Dim xmsg As String
If biz元データチェック(xpath, xmsg) = BIZ_PROC_NG Then
    MsgBox xmsg, vbExclamation
    Exit Sub
End If

良いところ

良いところ 内容
戻り値の意味が明確 関数の戻り値は処理ステータスだと分かる
呼び出し側が読みやすい = BIZ_PROC_NG と書ける
メッセージも自然に返せる ByRef a_err_msg に具体的な説明を入れられる
初級者にも説明しやすい 「戻り値はOK/NG、メッセージは引数」と説明できる
滝Libの方針と合う BIZ_PROC_OK / BIZ_PROC_NG をそのまま使える
UI層と分離しやすい 業務関数は表示せず、メッセージだけ返せる

悪いところ

悪いところ 内容
ByRef 引数が増える 関数の引数が少し長くなる
本来の戻り値を返しにくい 関数の戻り値をステータスに使うため、データは別の ByRef で返す必要がある
呼び出し側にメッセージ変数が必要 毎回 Dim xmsg As String が必要になる

ただし、これらの弱点は、業務マクロでは大きな問題になりにくいものです。むしろ、明示的にメッセージ変数を用意することで、呼び出し側がエラー処理を意識しやすくなります。

今回の標準形は、この方式にします。

理由は、業務マクロの読みやすさ、滝Libの BIZ_PROC_OK / BIZ_PROC_NG との相性、初級者への説明しやすさのバランスがよいからです。

6. ステータスもメッセージもByRefで返す

三つ目は、ステータスもメッセージも ByRef 引数で返す方式です。

Public Sub biz元データチェック( _
    ByVal a_folder_path As String, _
    ByRef a_status As Long, _
    ByRef a_err_msg As String _
)

呼び出し側は次のようになります。

Dim xstatus As Long
Dim xmsg As String
Call biz元データチェック(xpath, xstatus, xmsg)
If xstatus = BIZ_PROC_NG Then
    MsgBox xmsg, vbExclamation
    Exit Sub
End If

良いところ

良いところ 内容
複数の値を返せる ステータス、メッセージ、その他の値を同じ考え方で返せる
戻り値を別用途に残せる Function にする場合、戻り値を別の値に使える
Sub として書ける 処理手続きとして見せたい場合には使える

悪いところ

悪いところ 内容
成功・失敗が呼び出し式に出ない Call ... だけでは、エラー判定が必要な関数だと分かりにくい
チェック漏れが起きやすい xstatus を確認し忘れてもコード上は自然に見える
引数の意味が重くなる 入力引数と出力引数が混ざりやすい

特に問題なのは、呼び出し側の見た目です。

Call biz元データチェック(xpath, xstatus, xmsg)

この一行だけを見ると、戻り値を確認しなければならない処理に見えにくくなります。

一方、次の形なら、呼び出し側でエラー判定していることが見えます。

If biz元データチェック(xpath, xmsg) = BIZ_PROC_NG Then

業務マクロでは、後から読む人に「ここで止まる可能性がある」と見えることが重要です。

ステータスもメッセージも ByRef で返す方式は、複数の戻り値を返す必要がある特殊な関数では使えます。しかし、処理ステータスの標準返却方式としては、戻り値を使うほうが読みやすくなります。

7. Booleanでエラー有無を返す

四つ目は、Boolean でエラー有無を返す方式です。

Public Function has元データエラー( _
    ByVal a_folder_path As String, _
    ByRef a_err_msg As String _
) As Boolean

呼び出し側は次のようになります。

If has元データエラー(xpath, xmsg) Then
    MsgBox xmsg, vbExclamation
    Exit Sub
End If

この形は、名前と戻り値が一致していれば非常に読みやすいものです。

良いところ

良いところ 内容
If と相性がよい If hasError(...) Then と自然に読める
単純な判定に向いている ある/ない、できる/できないの判定に向く
初級者にも分かりやすい True ならエラーあり、False ならエラーなしと説明できる

悪いところ

悪いところ 内容
処理ステータスとしては情報が少ない OK/NG以外の状態を増やしにくい
関数名に強く依存する True が良い意味か悪い意味か、名前で決まる
BIZ_PROC_OK / NG と混ぜると危険 数値ステータスと真偽値が混在する

Boolean 方式で特に大事なのは、関数名です。

If has元データエラー(xpath, xmsg) Then

これは読みやすいコードです。True なら「エラーがある」という意味だからです。

しかし、次の名前だと分かりにくくなります。

If 元データチェック(xpath, xmsg) Then

この場合、True が「チェックOK」なのか「エラーあり」なのか、名前だけでは分かりません。

BIZ_PROC_NGとBooleanを混ぜる危険

滝Libでは、次のように定義されています。

Public Const BIZ_PROC_OK = 0
Public Const BIZ_PROC_NG = 1

VBAでは、If 1 Then は真として扱われます。そのため、次のコードは、NGのときに中へ入ります。

If biz元データチェック(xpath, xmsg) Then
    ' BIZ_PROC_NG = 1 なので、NGのときにここへ入る
End If

一見すると動いているように見えます。しかし、これは「ステータス値をBooleanのように扱っている」ため、読み方として危険です。

さらに、VBAの True は内部的には -1 です。したがって、次のような比較は意図通りになりません。

If biz元データチェック(xpath, xmsg) = True Then

BIZ_PROC_NG は 1、True は -1 なので一致しません。

ステータス関数は、必ず明示比較します。

If biz元データチェック(xpath, xmsg) = BIZ_PROC_NG Then

has...、is...、can... のような純粋な判定関数では Boolean を使ってよいものです。一方、業務処理の成否を返す関数では、BIZ_PROC_OK / BIZ_PROC_NG を使うほうが安定します。

8. グローバル変数で最後のエラーを持つ

五つ目は、エラーメッセージをグローバル変数に入れる方式です。

Public BIZ_LAST_ERROR_MESSAGE As String
Public Function biz元データチェック(ByVal a_folder_path As String) As Long
    
    BIZ_LAST_ERROR_MESSAGE = vbNullString
    
    If a_folder_path = vbNullString Then
        BIZ_LAST_ERROR_MESSAGE = "元データフォルダが指定されていません。"
        biz元データチェック = BIZ_PROC_NG
        Exit Function
    End If
    
    biz元データチェック = BIZ_PROC_OK
End Function

呼び出し側は次のようになります。

If biz元データチェック(xpath) = BIZ_PROC_NG Then
    MsgBox BIZ_LAST_ERROR_MESSAGE, vbExclamation
    Exit Sub
End If

良いところ

良いところ 内容
引数が少なくなる ByRef a_err_msg を渡さなくてよい
仕組みとしては単純 最後のエラーを一か所に保存するだけでよい
小さいマクロでは書きやすい 処理が短ければ成立する場合もある

悪いところ

悪いところ 内容
どの関数のエラーか分かりにくい 最後に書き込んだ関数の値だけが残る
上書きされやすい 別の処理を呼ぶとメッセージが変わる
ネストに弱い 関数の中から別の関数を呼ぶと扱いが難しい
テストしにくい 関数の入出力だけで完結しない
状態管理が増える 初期化漏れや消し忘れが起きる

特に、業務マクロでは、処理が成長するにつれて関数呼び出しが深くなります。

実行処理
  -> 元データチェック
      -> ファイル一覧取得
      -> シートチェック
      -> 見出しチェック

このような構造になると、「最後のエラー」がどこでセットされたものか分かりにくくなります。

グローバル変数で最後のエラーを持つ方式は、小さい実験コードでは便利なことがあります。しかし、標準的な業務マクロでは避けます。

グローバル変数は、設定、ログ、キャッシュなど、共有状態として設計したものに限定したほうがよいでしょう。

9. 例外で返す

六つ目は、Err.Raise で例外を発生させる方式です。

If Not fso.FileExists(xpath) Then
    Err.Raise TK3_ERROR_NOT_FOUND, , "ファイルが存在しません。"
End If

例外は、処理を中断して上位のエラーハンドラへ飛ばせる強い仕組みです。

Public Sub 実行_Click()
    On Error GoTo EH
    
    Call 処理本体
    Exit Sub
    
EH:
    MsgBox Err.Description, vbCritical
End Sub

良いところ

良いところ 内容
想定外エラーを止められる 前提が壊れたときに処理を続行しない
下位層から上位層へ一気に返せる 深い関数からでもエラーを上げられる
開発中の不具合検出に向く 型不正、内部状態不整合などを止められる
ライブラリ層と相性がよい 汎用関数の前提違反を明確にできる

悪いところ

悪いところ 内容
通常の業務分岐が見えにくくなる ありえるNGまで例外にすると流れが追いにくい
初級者には難しい On Error GoTo と復帰位置の理解が必要
例外の握りつぶしが起きやすい On Error Resume Next と混ざると危険
ユーザー向けメッセージと混ざりやすい 開発者向けエラーと業務エラーの区別が崩れる

業務エラーと例外は分ける

業務マクロでは、次の二つを分ける必要があります。

種類 例 返し方
業務上ありえるNG 元データがない、見出しが足りない、マスタに存在しない BIZ_PROC_NG とメッセージ
プログラム上の異常 引数の型が違う、内部辞書が初期化されていない、ありえないステータス Err.Raise

顧客コードがマスタにないことは、業務上ありえるエラーです。これは BIZ_PROC_NG で返します。

If Not xdict.Exists(a_customer_cd) Then
    a_err_msg = "顧客コードがマスタに存在しません。" & vbCrLf & a_customer_cd
    Exit Function
End If

一方、顧客マスタ辞書そのものが Nothing であることは、プログラムの内部状態がおかしい状態です。

If xdict Is Nothing Then
    Err.Raise TK3_ERROR_STATUS, , "顧客マスタ辞書が初期化されていません。"
End If

例外は使います。ただし、業務ロジックからUI層へ通常の業務NGを返すための標準手段にはしません。

例外は、共通関数、ライブラリ、前提違反、内部不整合を止めるために使います。

10. Excelエラー値で返す

七つ目は、Excelのエラー値を返す方式です。

CVErr(xlErrNA)

これは、セルや配列の中に #N/A などのエラー値を入れるときに有効です。

xarr(i, j) = CVErr(xlErrNA)

良いところ

良いところ 内容
Excelの表現と相性がよい セル上で #N/A などとして扱える
データの欠損を表現できる 配列や表の一部だけエラーにできる
ワークシート関数と接続しやすい Excel側の計算結果として扱いやすい

悪いところ

悪いところ 内容
処理全体の成否には向かない 関数全体が成功したかどうかとは別問題
メッセージを持ちにくい CVErr だけでは具体的な説明が不足する
VBA側で扱いに注意が必要 IsError などの判定が必要になる

CVErr は、セル値や配列要素としてのエラー表現に使います。業務処理全体の成功・失敗を返す標準手段にはしません。

次の三つは混同しないようにします。

種類 用途
BIZ_PROC_OK / BIZ_PROC_NG 業務処理全体の成功・失敗
Err.Raise 想定外、前提違反、内部不整合
CVErr セルや配列の値としてのエラー

11. クラスやユーザー定義型で返す

八つ目は、処理結果をクラスやユーザー定義型で返す方式です。

イメージとしては次のような形です。

Public Type BizResultType
    Status As Long
    ErrNo As Long
    Message As String
End Type

または、クラスモジュールで BizResult を作ります。

Dim xresult As BizResult
Set xresult = biz元データチェック(xpath)
If xresult.Status = BIZ_PROC_NG Then
    MsgBox xresult.Message
End If

良いところ

良いところ 内容
意味が名前で見える Status、Message のようにプロパティ名で読める
拡張しやすい 警告、詳細情報、ログIDなどを追加しやすい
大きなシステムでは整理しやすい 戻り値の型として統一できる

悪いところ

悪いところ 内容
VBAでは少し重い クラスやTypeの運用が増える
初級者に説明しにくい オブジェクト設計の理解が必要になる
業務マクロには過剰になりやすい 小中規模のマクロでは仕組みが勝ちすぎる
標準化しないと混乱する 結果クラスの運用ルールが必要になる

大きなアプリケーションや、複雑な処理結果を扱うマクロでは検討できます。しかし、今回の標準形としては採用しません。

滝Libを使った一般的な業務マクロでは、まずは BIZ_PROC_OK / BIZ_PROC_NG と ByRef a_err_msg のほうが軽くて分かりやすいものです。

12. 今回の標準形

ここまでの候補を比較すると、今回の標準形は次のようになります。

戻り値           : BIZ_PROC_OK / BIZ_PROC_NG
エラーメッセージ : ByRef a_err_msg As String
正常時の出力値   : 必要に応じて ByRef
想定外エラー     : Err.Raise
セル値のエラー   : CVErr

つまり、業務ロジック関数は次の形を標準にします。

Public Function biz処理名( _
    ByVal a_input1 As String, _
    ByVal a_input2 As Long, _
    ByRef a_err_msg As String _
) As Long

正常時の出力値が必要な場合は、エラーメッセージの前に ByRef 出力引数を置きます。

Public Function biz顧客名取得( _
    ByVal a_customer_cd As String, _
    ByRef a_customer_name As String, _
    ByRef a_err_msg As String _
) As Long

引数の順番は、原則として次の順にします。

  1. 入力引数

  2. 正常時の出力引数

  3. エラーメッセージ

入力 -> 出力 -> エラーメッセージ

入力引数は、原則として ByVal にします。

ByVal a_customer_cd As String

値を返すための引数だけ ByRef にします。

ByRef a_customer_name As String
ByRef a_err_msg As String

これにより、入力と出力の区別がはっきりします。

13. 実装テンプレート

業務ロジック関数の標準テンプレートは次のとおりです。

Public Function biz処理名( _
    ByVal a_input As String, _
    ByRef a_err_msg As String _
) As Long
    
    biz処理名 = BIZ_PROC_NG
    a_err_msg = vbNullString
    
    If a_input = vbNullString Then
        a_err_msg = "入力値が空白です。"
        Exit Function
    End If
    
    ' 業務処理
    
    biz処理名 = BIZ_PROC_OK
End Function

先頭で BIZ_PROC_NG を入れておくのがポイントです。

biz処理名 = BIZ_PROC_NG

最後まで正しく進んだ場合だけ、BIZ_PROC_OK にします。

biz処理名 = BIZ_PROC_OK

この書き方にしておくと、途中で Exit Function した場合に、誤って正常終了になりにくくなります。

14. 正常時の出力値がある場合

顧客名を取得する例で考えます。

Public Function biz顧客名取得( _
    ByVal a_customer_cd As String, _
    ByRef a_customer_name As String, _
    ByRef a_err_msg As String _
) As Long
    
    biz顧客名取得 = BIZ_PROC_NG
    a_customer_name = vbNullString
    a_err_msg = vbNullString
    
    If a_customer_cd = vbNullString Then
        a_err_msg = "顧客コードが空白です。"
        Exit Function
    End If
    
    ' 本来はここで顧客マスタを検索する
    a_customer_name = "山田商店"
    
    biz顧客名取得 = BIZ_PROC_OK
End Function

呼び出し側は次のようになります。

Dim xcustomer_name As String
Dim xmsg As String
If biz顧客名取得(xcustomer_cd, xcustomer_name, xmsg) = BIZ_PROC_NG Then
    MsgBox xmsg, vbExclamation
    Exit Sub
End If
Debug.Print xcustomer_name

この形では、何がどこに返るかが明確です。

値 返し方
処理の成否 関数の戻り値
顧客名 ByRef a_customer_name
エラーメッセージ ByRef a_err_msg

15. 呼び出し側の標準形

呼び出し側は、戻り値を必ず明示比較します。

If biz元データチェック(xpath, xmsg) = BIZ_PROC_NG Then
    MsgBox xmsg, vbExclamation
    Exit Sub
End If

次のようには書きません。

If biz元データチェック(xpath, xmsg) Then

理由は、これは BIZ_PROC_NG = 1 をBooleanの True のように扱っているためです。動く場合はありますが、意味が曖昧になります。

厳密に書くなら、Select Case を使います。

Dim xstatus As Long
Dim xmsg As String
xstatus = biz元データチェック(xpath, xmsg)
Select Case xstatus
    Case BIZ_PROC_OK
        ' 続行
    
    Case BIZ_PROC_NG
        MsgBox xmsg, vbExclamation
        Exit Sub
    
    Case Else
        Err.Raise TK3_ERROR_STATUS, , "不正な処理ステータスです。"
End Select

Case Else に入るのは、業務エラーではなく、プログラム側の不整合です。したがって、Err.Raise で止めてよい状態です。

16. メッセージの作り方

a_err_msg に入れるメッセージは、上位層がそのまま表示しても意味が通る程度に具体的にします。

悪い例です。

エラーです。
失敗しました。

これでは、ユーザーが次に何をすればよいか分かりません。

よい例です。

a_err_msg = _
    "元データフォルダにファイルがありません。" & vbCrLf & _
    "対象フォルダ: " & a_folder_path & vbCrLf & _
    "元データファイルを1件配置してから再実行してください。"

メッセージには、できれば次の要素を入れます。

要素 内容
何が起きたか 元データフォルダにファイルがありません
どこで起きたか フォルダパス、シート名、行番号、項目名
どうすればよいか ファイルを1件配置して再実行してください

業務ロジック関数は、エラーの発生箇所や対象データを知っています。そのため、具体的なメッセージは業務ロジック関数側で作るのが自然です。

ただし、MsgBox のタイトル、アイコン、ボタンはUI層で決めます。

17. 警告の扱い

業務マクロでは、処理を止めるほどではないが、ユーザーに知らせたい状態もあります。

状態 扱い
任意項目が空白 警告として残し、処理継続
金額が通常より大きい 警告ログを出して処理継続
一部の値を補正した 補正ログを残して処理継続

この場合、BIZ_PROC_NG にするかどうかは、「処理を止めるべきか」で判断します。

処理を止めるなら BIZ_PROC_NG です。

a_err_msg = "必須項目が入力されていません。"
Exit Function

処理を止めないなら BIZ_PROC_OK のまま、警告ログなどに残します。

Call ログ追加("警告: 任意項目が空白です。行=" & xrow)

警告まで ByRef で返す設計もあります。

Public Function biz売上データ作成( _
    ByRef a_warn_msg As String, _
    ByRef a_err_msg As String _
) As Long

ただし、最初から全関数に警告メッセージを持たせると重くなります。標準形は a_err_msg だけにし、警告が重要な処理だけ個別に設計するほうがよいでしょう。

18. UI層の標準形

UI層、つまりボタンや実行入口では、業務エラーと例外を分けて受けます。

Public Sub 実行_Click()
    On Error GoTo EH
    
    Dim xmsg As String
    
    If bizメイン処理(xmsg) = BIZ_PROC_NG Then
        MsgBox xmsg, vbExclamation, "処理を中止しました"
        Exit Sub
    End If
    
    MsgBox "処理が完了しました。", vbInformation, "完了"
    Exit Sub
    
EH:
    MsgBox _
        "想定外のエラーが発生しました。" & vbCrLf & _
        "Err.Number: " & Err.Number & vbCrLf & _
        "Err.Description: " & Err.Description, _
        vbCritical, _
        "システムエラー"
End Sub

この形にすると、次のように分かれます。

種類 受け方
業務上のNG If ... = BIZ_PROC_NG Then
想定外の例外 On Error GoTo EH

業務上のNGは通常の分岐です。例外は最後の安全網です。

19. 例外的に業務関数内でMsgBoxを許す場合

原則として、業務ロジック関数は MsgBox を出しません。

ただし、VBAでは処理の途中でユーザー確認を行い、その回答によって処理を続けるか止めるかを決める構造が書きにくい場面があります。

たとえば、次のような処理です。

既存ファイルがあります。
上書きしますか。
はいなら続行。
いいえなら中止。

この場合、確認そのものが業務処理の途中に入り込みます。上位層へいったん戻して、再開するような構造はVBAでは大げさになりやすいものです。

そのため、ユーザー確認が不可避な場合だけ、業務関数内で MsgBox を使うことを例外的に認めてもよいでしょう。

ただし、この場合も方針を決めておきます。

  1. 単なるエラー表示では使わない。

  2. ユーザーの選択で処理分岐する場合に限定する。

  3. 関数名やコメントで、UI確認を含むことを分かるようにする。

20. 命名規則と戻り値の意味をそろえる

戻り値の意味と関数名は一致させます。

関数名 戻り値 用途
biz...チェック BIZ_PROC_OK / BIZ_PROC_NG 業務チェック
biz...実行 BIZ_PROC_OK / BIZ_PROC_NG 業務処理
biz...作成 BIZ_PROC_OK / BIZ_PROC_NG 業務データ作成
has... Boolean ある/ない
is... Boolean そうである/そうでない
can... Boolean できる/できない

たとえば、次はよい形です。

If has元データエラー(xpath, xmsg) Then

戻り値が Boolean で、名前も「エラーがあるか」になっています。

次もよい形です。

If biz元データチェック(xpath, xmsg) = BIZ_PROC_NG Then

戻り値が業務ステータスで、明示比較しています。

避けたいのは、次のような混在です。

If biz元データチェック(xpath, xmsg) Then

これでは、関数が Boolean を返しているように見えます。しかし実際には BIZ_PROC_OK / BIZ_PROC_NG の数値ステータスです。

21. 実務上のルール

滝Libを使ったExcel VBA業務マクロでは、標準形を次のようにします。

業務ロジック関数の戻り値 : BIZ_PROC_OK / BIZ_PROC_NG
エラーメッセージ         : ByRef a_err_msg As String
正常時のデータ           : 必要に応じて ByRef 出力引数
業務上想定されるNG       : BIZ_PROC_NG
想定外・内部不整合       : Err.Raise
セルや配列内のエラー値   : CVErr

実務上のルールは次のとおりです。

  1. 業務ロジック関数の戻り値は Long にする。

  2. 戻り値は BIZ_PROC_OK または BIZ_PROC_NG にする。

  3. エラーメッセージは ByRef a_err_msg As String に返す。

  4. 正常時の出力値がある場合は、別の ByRef 引数で返す。

  5. 入力引数は原則 ByVal にする。

  6. 関数冒頭で戻り値を BIZ_PROC_NG にする。

  7. 関数冒頭で a_err_msg を vbNullString にする。

  8. 最後まで正常に進んだ場合だけ BIZ_PROC_OK にする。

  9. 呼び出し側では、戻り値を必ず = BIZ_PROC_NG のように明示比較する。

  10. If biz処理名(...) Then のようにBoolean扱いしない。

  11. has...、is...、can... の関数だけ Boolean を返す。

  12. 業務上ありえるエラーは例外にしない。

  13. 前提違反、型不正、内部状態不整合は Err.Raise にする。

  14. セルや配列の一部のエラー表現には CVErr を使う。

  15. 配列返却は汎用ヘルパーや内部用途に限定し、標準の業務関数では使いすぎない。

  16. グローバル変数で最後のエラーメッセージを持つ方式は原則避ける。

22. なぜ今回はこの方式にするのか

今回の対象は、汎用ライブラリそのものではなく、滝Libを使ったExcel業務マクロです。

業務マクロは、専門の開発者だけが読むとは限りません。後から保守する人、業務担当者に近い人、VBAに慣れていない人も読む可能性があります。

その前提では、抽象度の高い配列返却や結果オブジェクトよりも、次のように読める形のほうが強いものです。

If biz元データチェック(xpath, xmsg) = BIZ_PROC_NG Then
    MsgBox xmsg, vbExclamation
    Exit Sub
End If

このコードは、かなり素直に読めます。

元データチェックをする。
NGならメッセージを出して止める。
OKなら次へ進む。

業務マクロのエラー処理では、この素直さが重要です。

配列で返す方法もあります。Boolean で返す方法もあります。例外で返す方法もあります。どれも間違いではありません。

ただし、今回の標準としては、次のバランスを優先します。

観点 優先すること
読みやすさ 呼び出し側でOK/NGが見える
説明しやすさ 戻り値はステータス、ByRef はメッセージ
滝Libとの相性 BIZ_PROC_OK / BIZ_PROC_NG を使う
保守性 Boolean、例外、CVErr と役割を混ぜない
初級者への伝達 配列添字や結果オブジェクトを標準にしない

したがって、今回の標準は次の一文でまとめられます。

業務ロジックの成否は戻り値で返し、具体的なエラーメッセージはByRefで返す。

これが、滝Libを使ったExcel VBA業務マクロでは、もっとも現実的で読みやすいエラー返却設計です。

出典メモ

本記事は、C:\Users\hoehoe\Downloads\biz_logic_error_return_design.md をもとに、LWP公開記事として再構成したものです。

原稿の主張である「業務ロジックの成否は BIZ_PROC_OK / BIZ_PROC_NG で返し、具体的なエラーメッセージは ByRef a_err_msg As String で返す」という骨格は維持しました。公開記事化にあたり、既存記事「VBA業務マクロのエラー処理を三層と三分類で設計する」と「VBA業務マクロの関数命名をレイヤーと責務で設計する」との重複を避け、戻り値設計そのものに焦点が当たるように、導入、対象読者、比較表、標準テンプレート、命名との接続を整理しました。