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 を業務ロジックの標準形として使いたい人
本記事で得られること
業務ロジック関数から返すべき情報を、処理ステータスとエラーメッセージに分けて考えられます。
BIZ_PROC_OK / BIZ_PROC_NG と ByRef a_err_msg を使った標準テンプレートを持てます。
Boolean、配列返却、グローバル変数、例外、CVErr、結果オブジェクトを、用途別に使い分けられます。
業務上ありえるNGと、プログラム上の内部不整合を分けて扱えます。
UI層、業務ロジック層、共通関数・ライブラリ層の責務を混ぜずに設計できます。
1. 業務ロジックから返したい情報は二つある
業務マクロの業務ロジック関数から上位層へ返したい情報は、大きく二つあります。
処理が正常終了したか、エラー終了したか
エラー終了した場合、その具体的な内容は何か
たとえば、元データフォルダをチェックする関数を考えます。
Call 元データチェック()この関数の中では、次のようなことを確認します。
元データフォルダが存在するか
フォルダ内にファイルがあるか
ファイルが複数入っていないか
対象ファイルに必要なシートがあるか
シートの見出しに必要項目があるか
このとき、呼び出し側が知りたいことは、単に「成功したか」だけではありません。
失敗したことなぜ失敗したのかどこを直せばよいのかこの三つが分からないと、ユーザーに案内できません。
したがって、業務ロジック関数は、少なくとも次の二つを返す必要があります。
| 情報 | 例 |
|---|---|
| 処理ステータス | 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 FunctionEnd If代わりに、メッセージを返します。
If xcount = 0 Then a_err_msg = "元データがありません。" Exit FunctionEnd If表示は上位層で行います。
If biz元データチェック(xpath, xmsg) = BIZ_PROC_NG Then MsgBox xmsg, vbExclamation Exit SubEnd 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 xresultxresult = biz元データチェック(xpath)If xresult(0) = BIZ_PROC_NG Then MsgBox xresult(2), vbExclamation Exit SubEnd 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 = 0Public Const BIZ_RESULT_ERRNO = 1Public Const BIZ_RESULT_MSG = 2If xresult(BIZ_RESULT_STATUS) = BIZ_PROC_NG Then MsgBox xresult(BIZ_RESULT_MSG), vbExclamationEnd 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_OKEnd Function呼び出し側は次のようになります。
Dim xmsg As StringIf biz元データチェック(xpath, xmsg) = BIZ_PROC_NG Then MsgBox xmsg, vbExclamation Exit SubEnd 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 LongDim xmsg As StringCall biz元データチェック(xpath, xstatus, xmsg)If xstatus = BIZ_PROC_NG Then MsgBox xmsg, vbExclamation Exit SubEnd 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 SubEnd 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 = 0Public Const BIZ_PROC_NG = 1VBAでは、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 ThenBIZ_PROC_NG は 1、True は -1 なので一致しません。
ステータス関数は、必ず明示比較します。
If biz元データチェック(xpath, xmsg) = BIZ_PROC_NG Thenhas...、is...、can... のような純粋な判定関数では Boolean を使ってよいものです。一方、業務処理の成否を返す関数では、BIZ_PROC_OK / BIZ_PROC_NG を使うほうが安定します。
8. グローバル変数で最後のエラーを持つ
五つ目は、エラーメッセージをグローバル変数に入れる方式です。
Public BIZ_LAST_ERROR_MESSAGE As StringPublic 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_OKEnd Function呼び出し側は次のようになります。
If biz元データチェック(xpath) = BIZ_PROC_NG Then MsgBox BIZ_LAST_ERROR_MESSAGE, vbExclamation Exit SubEnd 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, vbCriticalEnd 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 FunctionEnd 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 StringEnd Typeまたは、クラスモジュールで BizResult を作ります。
Dim xresult As BizResultSet xresult = biz元データチェック(xpath)If xresult.Status = BIZ_PROC_NG Then MsgBox xresult.MessageEnd 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引数の順番は、原則として次の順にします。
入力引数
正常時の出力引数
エラーメッセージ
入力 -> 出力 -> エラーメッセージ入力引数は、原則として ByVal にします。
ByVal a_customer_cd As String値を返すための引数だけ ByRef にします。
ByRef a_customer_name As StringByRef 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_OKEnd 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_OKEnd Function呼び出し側は次のようになります。
Dim xcustomer_name As StringDim xmsg As StringIf biz顧客名取得(xcustomer_cd, xcustomer_name, xmsg) = BIZ_PROC_NG Then MsgBox xmsg, vbExclamation Exit SubEnd IfDebug.Print xcustomer_nameこの形では、何がどこに返るかが明確です。
| 値 | 返し方 |
|---|---|
| 処理の成否 | 関数の戻り値 |
| 顧客名 | ByRef a_customer_name |
| エラーメッセージ | ByRef a_err_msg |
15. 呼び出し側の標準形
呼び出し側は、戻り値を必ず明示比較します。
If biz元データチェック(xpath, xmsg) = BIZ_PROC_NG Then MsgBox xmsg, vbExclamation Exit SubEnd If次のようには書きません。
If biz元データチェック(xpath, xmsg) Then理由は、これは BIZ_PROC_NG = 1 をBooleanの True のように扱っているためです。動く場合はありますが、意味が曖昧になります。
厳密に書くなら、Select Case を使います。
Dim xstatus As LongDim xmsg As Stringxstatus = 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 SelectCase 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 を使うことを例外的に認めてもよいでしょう。
ただし、この場合も方針を決めておきます。
単なるエラー表示では使わない。
ユーザーの選択で処理分岐する場合に限定する。
関数名やコメントで、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実務上のルールは次のとおりです。
業務ロジック関数の戻り値は Long にする。
戻り値は BIZ_PROC_OK または BIZ_PROC_NG にする。
エラーメッセージは ByRef a_err_msg As String に返す。
正常時の出力値がある場合は、別の ByRef 引数で返す。
入力引数は原則 ByVal にする。
関数冒頭で戻り値を BIZ_PROC_NG にする。
関数冒頭で a_err_msg を vbNullString にする。
最後まで正常に進んだ場合だけ BIZ_PROC_OK にする。
呼び出し側では、戻り値を必ず = BIZ_PROC_NG のように明示比較する。
If biz処理名(...) Then のようにBoolean扱いしない。
has...、is...、can... の関数だけ Boolean を返す。
業務上ありえるエラーは例外にしない。
前提違反、型不正、内部状態不整合は Err.Raise にする。
セルや配列の一部のエラー表現には CVErr を使う。
配列返却は汎用ヘルパーや内部用途に限定し、標準の業務関数では使いすぎない。
グローバル変数で最後のエラーメッセージを持つ方式は原則避ける。
22. なぜ今回はこの方式にするのか
今回の対象は、汎用ライブラリそのものではなく、滝Libを使ったExcel業務マクロです。
業務マクロは、専門の開発者だけが読むとは限りません。後から保守する人、業務担当者に近い人、VBAに慣れていない人も読む可能性があります。
その前提では、抽象度の高い配列返却や結果オブジェクトよりも、次のように読める形のほうが強いものです。
If biz元データチェック(xpath, xmsg) = BIZ_PROC_NG Then MsgBox xmsg, vbExclamation Exit SubEnd 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業務マクロの関数命名をレイヤーと責務で設計する」との重複を避け、戻り値設計そのものに焦点が当たるように、導入、対象読者、比較表、標準テンプレート、命名との接続を整理しました。
