ねらい:あらゆる「入力ミス」「設定ミス」を一箇所で検査できる型を持つ
バリデーション総合テンプレのゴールは、こうです。
「動かしてみたら途中で落ちた」「設定ミスに気づくのが遅い」をやめて、
マクロを本処理に入る前に「おかしなところがないか」を一気にチェックできる“関所”を作ることです。
ここでは、次の3つをまとめて扱えるテンプレを作ります。
シートやセルの「入力値チェック」
設定シート(Config系)の「設定内容チェック」
フォーム入力の「UIバリデーション」
全部をバラバラに書くのではなく、「バリデーション専用の考え方とコードの型」を決めておく——これがポイントです。
全体設計:バリデーションを「専用モジュール+専用関数」に切り出す
なぜ“専用モジュール”にするのか
バリデーションを各所にバラバラに書くと、こうなります。
どこで何をチェックしているか分からない
同じチェックを何度も書く
仕様変更のたびに、あちこち直す羽目になる
これを避けるために、「バリデーションは Validation モジュールに集約する」というルールを決めます。
例として、こんなモジュール構成をイメージしてください。
ModValidation
入力値チェック・設定チェック・共通チェックをまとめるモジュール
そして、各処理の入口で必ずこうします。
本処理の前に「Validate○○」を呼ぶ
False(失敗)なら、その場で中断してメッセージを出す
True(成功)なら、本処理に進む
この「必ず関所を通る」という型を作るのが、総合テンプレの核です。
コア部品1:シート入力バリデーションテンプレ
例題:顧客マスタシートの入力チェック
顧客マスタシート(Customer)に、こんな列があるとします。
A列:顧客コード(必須・重複禁止)
B列:顧客名(必須)
C列:売上(数値・0以上)
このシートを使ってJOINや集計をする前に、「入力としておかしくないか」をチェックしたい、という場面です。
バリデーション関数の型
ModValidation に、次のような関数を用意します。
' ModValidation.bas
Option Explicit
Public Function ValidateCustomerSheet() As Boolean
Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Worksheets("Customer")
On Error GoTo 0
If ws Is Nothing Then
MsgBox "Customer シートが存在しません。", vbExclamation
ValidateCustomerSheet = False
Exit Function
End If
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
If lastRow < 2 Then
MsgBox "Customer シートにデータがありません。", vbExclamation
ValidateCustomerSheet = False
Exit Function
End If
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
Dim i As Long
For i = 2 To lastRow
Dim code As String
code = Trim(CStr(ws.Cells(i, "A").Value))
Dim name As String
name = Trim(CStr(ws.Cells(i, "B").Value))
Dim sales As Variant
sales = ws.Cells(i, "C").Value
If code = "" Then
MsgBox "Customer!A" & i & " 顧客コードが空です。", vbExclamation
ValidateCustomerSheet = False
Exit Function
End If
If dict.Exists(code) Then
MsgBox "顧客コードが重複しています: " & code & "(行 " & i & ")", vbExclamation
ValidateCustomerSheet = False
Exit Function
Else
dict.Add code, True
End If
If name = "" Then
MsgBox "Customer!B" & i & " 顧客名が空です。", vbExclamation
ValidateCustomerSheet = False
Exit Function
End If
If Not IsNumeric(sales) Or sales < 0 Then
MsgBox "Customer!C" & i & " 売上が数値でないか、0未満です。", vbExclamation
ValidateCustomerSheet = False
Exit Function
End If
Next i
ValidateCustomerSheet = True
End Function
VBここでの重要ポイントをかみ砕きます。
シートの存在チェック
データ行があるかチェック
行ループで「必須」「重複」「型・範囲」をチェック
エラーを見つけたら、その場でメッセージを出し、False を返して終了
「最初のエラーで止める」か「全部のエラーを集める」かは好みですが、
まずは“最初のエラーで止める”型の方がシンプルで実務向きです。
本処理からの呼び出し方
顧客マスタを使う処理の入口で、こう書きます。
Sub JoinCustomer()
Const MODULE_NAME As String = "JoinCustomer"
On Error GoTo ErrHandler
If Not ValidateCustomerSheet() Then
Exit Sub
End If
' ここからJOIN本処理
Exit Sub
ErrHandler:
LogError MODULE_NAME, "MAIN", Err
End Sub
VB「Validate○○がTrueなら本処理へ」というパターンを徹底すると、
どの処理も“必ず関所を通る”構造になります。
コア部品2:設定シート(Config系)のバリデーションテンプレ
なぜ設定シートのチェックが重要か
設定シート方式のツール(JOIN・集計・変換・差分など)は、
「設定が間違っていると、コードは正しくても結果が壊れる」という性質があります。
だからこそ、「Config○○を読み込んだ直後に、設定内容を検査する」テンプレを持っておくと強いです。
例題:JOIN設定(ConfigJoin)のバリデーション
ConfigJoin に、こんな列があるとします。
A列:Enabled(Y/N)
B列:LeftSheet
C列:LeftKeyCol
D列:RightSheet
E列:RightKeyCol
F列:JoinType(INNER/LEFT など)
これを読み込んだ後に、「シートが存在するか」「列指定が正しいか」「JoinType が許可された値か」をチェックします。
ルール配列を検査する関数
まず、JOINルールの構造体を想定します。
Private Type JoinRule
Enabled As Boolean
LeftSheet As String
LeftKeyCol As Long
RightSheet As String
RightKeyCol As Long
JoinType As String
End Type
VBLoadJoinRules で rules As Variant(JoinRule配列)を読み込んだ後、
次のような検証関数を呼びます。
Public Function ValidateJoinRules(ByRef rules As Variant) As Boolean
Dim i As Long
For i = LBound(rules) To UBound(rules)
If rules(i).Enabled Then
If Not SheetExists(rules(i).LeftSheet) Then
MsgBox "JOIN設定エラー: LeftSheet [" & rules(i).LeftSheet & "] が存在しません。(ルール " & i & ")", vbExclamation
ValidateJoinRules = False
Exit Function
End If
If Not SheetExists(rules(i).RightSheet) Then
MsgBox "JOIN設定エラー: RightSheet [" & rules(i).RightSheet & "] が存在しません。(ルール " & i & ")", vbExclamation
ValidateJoinRules = False
Exit Function
End If
If Not ColumnExists(rules(i).LeftSheet, rules(i).LeftKeyCol) Then
MsgBox "JOIN設定エラー: LeftKeyCol が不正です。(ルール " & i & ")", vbExclamation
ValidateJoinRules = False
Exit Function
End If
If Not ColumnExists(rules(i).RightSheet, rules(i).RightKeyCol) Then
MsgBox "JOIN設定エラー: RightKeyCol が不正です。(ルール " & i & ")", vbExclamation
ValidateJoinRules = False
Exit Function
End If
Select Case UCase$(rules(i).JoinType)
Case "INNER", "LEFT"
' OK
Case Else
MsgBox "JOIN設定エラー: JoinType [" & rules(i).JoinType & "] は許可されていません。(ルール " & i & ")", vbExclamation
ValidateJoinRules = False
Exit Function
End Select
End If
Next i
ValidateJoinRules = True
End Function
VBここで使っている補助関数も、ModValidation か ModConfigUtil に置いておきます。
Public Function SheetExists(ByVal sheetName As String) As Boolean
On Error Resume Next
SheetExists = Not ThisWorkbook.Worksheets(sheetName) Is Nothing
On Error GoTo 0
End Function
Public Function ColumnExists(ByVal sheetName As String, ByVal colNum As Long) As Boolean
Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Worksheets(sheetName)
On Error GoTo 0
If ws Is Nothing Then
ColumnExists = False
Else
ColumnExists = (colNum >= 1 And colNum <= ws.Columns.Count)
End If
End Function
VBJOINエンジン側では、こう使います。
Public Sub RunJoinTool()
Dim rules As Variant
rules = LoadJoinRules()
If IsEmpty(rules) Then Exit Sub
If Not ValidateJoinRules(rules) Then
Exit Sub
End If
' ここからJOIN本処理
End Sub
VB「設定を読んだら、必ず Validate○○Rules を通す」という型を作るのが、総合テンプレの大事な部分です。
コア部品3:フォーム入力のバリデーションテンプレ
例題:日付+モード選択フォーム
前に作った frmMain(txtDate, cboMode)を思い出してください。
フォーム側の ValidateInput を、バリデーションテンプレとして整理します。
Private Function ValidateInput() As Boolean
If Trim(Me.txtDate.Value) = "" Then
MsgBox "対象日を入力してください。", vbExclamation
Me.txtDate.SetFocus
ValidateInput = False
Exit Function
End If
If IsDate(Me.txtDate.Value) = False Then
MsgBox "対象日が日付として正しくありません。", vbExclamation
Me.txtDate.SetFocus
ValidateInput = False
Exit Function
End If
If Me.cboMode.ListIndex < 0 Then
MsgBox "処理モードを選択してください。", vbExclamation
Me.cboMode.SetFocus
ValidateInput = False
Exit Function
End If
ValidateInput = True
End Function
VBフォームのバリデーションで大事なのは、「ユーザーにどこを直せばいいかを即座に伝える」ことです。
エラーメッセージは具体的に
SetFocus でフォーカスを戻す
必要なら、エラー箇所を色付けする
このあたりを“型”として持っておくと、どのフォームでも同じ操作感になります。
重要ポイントの深掘り:バリデーション総合テンプレを“本当に使える武器”にする
1:バリデーションは「本処理の前に一気にやる」
処理の途中で「やっぱりこの値おかしいな」と気づくより、
最初に「入力」「設定」「環境」を一気にチェックした方が、ユーザーにも開発者にも優しいです。
入口でまとめてチェックする
エラーがあれば、その場で止めてメッセージ
通ったら、あとは“信じて”本処理を流す
この構造にすると、本処理のコードから「細かい防御コード」をかなり減らせます。
2:チェック内容を“関数名”で表現する
ValidateCustomerSheet
ValidateJoinRules
ValidateConfigBatch
ValidateFormMain
このように、「何を検査しているか」が関数名で分かるようにしておくと、
コードを読むときに「ここで何を守っているのか」が一瞬で伝わります。
逆に、If文をその場で書き散らすと、「何を守りたかったのか」がすぐに分からなくなります。
3:エラーメッセージは“未来の自分”へのメモだと思って書く
バリデーションのメッセージは、単なるユーザー向けだけではありません。
半年後の自分や、別の人が見たときに「何がダメで止まったのか」を理解するための手がかりです。
どのシートの
どのセル(行・列)で
何がどうおかしいのか
ここまで入れておくと、トラブル対応のスピードが段違いになります。
まとめ:バリデーション総合テンプレは「全部の処理の前に置く関所」
今日のテンプレを一言でまとめると、こうなります。
バリデーション専用モジュール(ModValidation)を用意し、
入力シート・設定シート・フォームごとに Validate○○ 関数を作る。
各処理の入口で「Validate○○がTrueなら本処理へ、Falseなら即終了」という型を徹底する。
これを一度仕組みとして作ってしまえば、
「とりあえず動かしてみて、落ちたら原因を探す」という世界から、
「動かす前におかしなところを教えてくれる」世界に変わります。
