Excel VBA 逆引き集 | 実務テンプレ完全版(超再利用部品) – モジュール構成テンプレ

Excel VBA Excel VBA
スポンサーリンク

ねらい:VBAプロジェクトを「迷子にならないモジュール構成」にする

モジュール構成テンプレのゴールは、こうです。
「新しい処理を追加するときに、どのモジュールに書けばいいか迷わない」
「半年後に開いても、“ああ、このモジュールはこの役割ね”とすぐ分かる」。

そのために、役割ごとにモジュールを分け、名前と中身のルールを決めておきます。
今日は、その“標準構成”を、初心者でも真似しやすい形でテンプレ化していきます。


全体像:標準モジュール構成のパターン

基本のモジュール分類

まず、大枠としてこう分けます。

業務の入口をまとめるモジュール。
業務フロー(バッチ)を制御するモジュール。
ノーコード系エンジン(JOIN・集計・差分・変換など)のモジュール。
共通基盤(ログ・時間計測・設定ユーティリティなど)のモジュール。
必要に応じてクラスモジュール(StopWatch など)。

これを、具体的な名前に落とすと、こんな構成になります。

ModEntry
ModBatchEngine
ModJoinEngine
ModAggEngine
ModDiffEngine
ModConvEngine
ModLogger
ModStopWatch
ModConfigUtil

ここに、業務ごとの「小さな業務マクロ」を追加していくイメージです。


エントリーモジュール:ユーザーが触る入口を集約する

ModEntry の役割

ModEntry は、「ユーザーが直接実行するマクロだけを置く場所」です。
ボタンに割り当てるのも、ショートカットキーに割り当てるのも、ここにある Sub だけにします。

例えば、日次処理の入口はこう書きます。

' ModEntry.bas
Option Explicit

Public Sub RunDailyJobs()
    Const MODULE_NAME As String = "RunDailyJobs"
    On Error GoTo ErrHandler
    
    LogStart MODULE_NAME, "日次バッチ開始"
    
    RunBatch
    
    LogEnd MODULE_NAME, "日次バッチ終了"
    Exit Sub
    
ErrHandler:
    LogError MODULE_NAME, "MAIN", Err
    MsgBox "日次処理中にエラーが発生しました。ログを確認してください。", vbExclamation
End Sub
VB

ここで重要なのは、業務ロジックを一切書かないことです。
やるのは「ログ開始」「バッチ呼び出し」「ログ終了」「エラー捕捉」だけ。
“入口専用モジュール”にしておくと、ユーザーが触る場所と内部ロジックがきれいに分かれます。


バッチモジュール:業務フローを一箇所に集める

ModBatchEngine の役割

ModBatchEngine は、「どの業務マクロを、どの順番で、どう実行するか」を管理する司令塔です。
ConfigBatch シートを読み込み、そこに書かれた MacroName を順番に実行します。

ジョブ1件分を表す構造体はこう定義します。

' ModBatchEngine.bas
Option Explicit

Private Type BatchJob
    Enabled As Boolean
    JobName As String
    MacroName As String
    StopOnError As Boolean
    Comment As String
End Type
VB

ConfigBatch を読み込む関数はこうです。

Private Function LoadBatchJobs() As Variant
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Worksheets("ConfigBatch")
    
    Dim lastRow As Long
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
    If lastRow < 2 Then
        LoadBatchJobs = Empty
        Exit Function
    End If
    
    Dim data As Variant
    data = ws.Range(ws.Cells(2, 1), ws.Cells(lastRow, 5)).Value
    
    Dim jobs() As BatchJob
    ReDim jobs(1 To UBound(data, 1))
    
    Dim i As Long
    For i = 1 To UBound(data, 1)
        jobs(i).Enabled = (UCase$(CStr(data(i, 1))) = "Y")
        jobs(i).JobName = CStr(data(i, 2))
        jobs(i).MacroName = CStr(data(i, 3))
        jobs(i).StopOnError = (UCase$(CStr(data(i, 4))) = "Y")
        jobs(i).Comment = CStr(data(i, 5))
    Next
    
    LoadBatchJobs = jobs
End Function
VB

メインの RunBatch はこうなります。

Public Sub RunBatch()
    Const MODULE_NAME As String = "BatchRunner"
    
    Dim jobs As Variant
    jobs = LoadBatchJobs()
    If IsEmpty(jobs) Then
        MsgBox "ConfigBatchにジョブがありません。", vbInformation
        Exit Sub
    End If
    
    SpeedOn
    
    LogStart MODULE_NAME, "バッチ処理開始"
    
    Dim i As Long
    For i = LBound(jobs) To UBound(jobs)
        If jobs(i).Enabled Then
            If Not RunOneJob(jobs(i), MODULE_NAME) Then
                If jobs(i).StopOnError Then
                    LogWrite "WARN", MODULE_NAME, "BATCH_STOP", _
                             "ジョブ[" & jobs(i).JobName & "]でエラー。StopOnError=Yのため中断。"
                    Exit For
                End If
            End If
        End If
    Next i
    
    LogEnd MODULE_NAME, "バッチ処理終了"
    
    SpeedOff
End Sub
VB

RunOneJob は、MacroName を Application.Run で呼び出すだけです。

Private Function RunOneJob(ByRef job As BatchJob, ByVal batchModuleName As String) As Boolean
    On Error GoTo ErrHandler
    
    LogWrite "INFO", batchModuleName, "JOB_START", _
             "ジョブ開始: " & job.JobName & " (" & job.MacroName & ")"
    
    Application.Run job.MacroName
    
    LogWrite "INFO", batchModuleName, "JOB_END", "ジョブ終了: " & job.JobName
    RunOneJob = True
    Exit Function
    
ErrHandler:
    LogWrite "ERROR", batchModuleName, "JOB_ERROR", _
             "ジョブ[" & job.JobName & "]でエラー発生。Err=" & Err.Number & ", " & Err.Description
    RunOneJob = False
End Function
VB

ここでの“モジュール構成的な”ポイントは、業務フローの制御を ModBatchEngine に集約することです。
個々の業務マクロは「自分の仕事だけ」を書き、順番やエラー時の振る舞いはバッチ側に任せます。


エンジン系モジュール:JOIN・集計・差分・変換を役割ごとに分ける

役割ごとにモジュールを分ける理由

ModJoinEngine には JOIN 専用のロジックだけ。
ModAggEngine には 集計専用のロジックだけ。
ModDiffEngine には 差分専用のロジックだけ。
ModConvEngine には 変換専用のロジックだけ。

こう分けることで、「JOINの仕様を変えたい」と思ったときに、見るべきモジュールが一つに絞れます。
逆に、全部を ModMain みたいな一つのモジュールに詰め込むと、
どこに何が書いてあるか分からなくなり、修正のたびに地獄を見ます。

典型例:ModConvEngine の骨格

' ModConvEngine.bas
Option Explicit

Private Type ConvRule
    Enabled As Boolean
    SourceSheet As String
    SourceCol As Long
    TargetSheet As String
    TargetCol As Long
    TransformName As String
    Options As String
End Type

Private Function LoadConvRules() As Variant
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Worksheets("ConfigConv")
    
    Dim lastRow As Long
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
    If lastRow < 2 Then
        LoadConvRules = Empty
        Exit Function
    End If
    
    Dim data As Variant
    data = ws.Range(ws.Cells(2, 1), ws.Cells(lastRow, 7)).Value
    
    Dim rules() As ConvRule
    ReDim rules(1 To UBound(data, 1))
    
    Dim i As Long
    For i = 1 To UBound(data, 1)
        rules(i).Enabled = (UCase$(CStr(data(i, 1))) = "Y")
        rules(i).SourceSheet = CStr(data(i, 2))
        rules(i).SourceCol = ColToNumber(data(i, 3))
        rules(i).TargetSheet = CStr(data(i, 4))
        rules(i).TargetCol = ColToNumber(data(i, 5))
        rules(i).TransformName = CStr(data(i, 6))
        rules(i).Options = CStr(data(i, 7))
    Next
    
    LoadConvRules = rules
End Function

Public Sub RunConvTool()
    Dim rules As Variant
    rules = LoadConvRules()
    If IsEmpty(rules) Then Exit Sub
    
    SpeedOn
    
    Dim i As Long
    For i = LBound(rules) To UBound(rules)
        If rules(i).Enabled Then
            ApplyConvRule rules(i)
        End If
    Next i
    
    SpeedOff
End Sub
VB

ApplyConvRule の中で、Application.Run で TransformName を呼び出します。
JOIN・集計・差分も、同じパターンでモジュールを分けておくと、構造が揃ってとても見通しが良くなります。


共通基盤モジュール:全プロジェクトで使い回す“土台”

ModLogger:ログ専用モジュール

ログ関連は、必ず一つのモジュールにまとめます。
どのモジュールからも同じ LogWrite / LogStart / LogEnd / LogError を呼ぶ形にします。

' ModLogger.bas
Option Explicit

Public Sub LogWrite( _
        ByVal level As String, _
        ByVal moduleName As String, _
        ByVal eventName As String, _
        Optional ByVal message As String = "")
    
    Dim ws As Worksheet
    On Error Resume Next
    Set ws = ThisWorkbook.Worksheets("Log")
    On Error GoTo 0
    
    If ws Is Nothing Then
        Set ws = ThisWorkbook.Worksheets.Add
        ws.Name = "Log"
        ws.Range("A1:E1").Value = Array("DateTime", "Level", "Module", "Event", "Message")
    End If
    
    Dim nextRow As Long
    nextRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row + 1
    
    ws.Cells(nextRow, 1).Value = Now
    ws.Cells(nextRow, 2).Value = UCase$(level)
    ws.Cells(nextRow, 3).Value = moduleName
    ws.Cells(nextRow, 4).Value = eventName
    ws.Cells(nextRow, 5).Value = message
End Sub

Public Sub LogStart(ByVal moduleName As String, Optional ByVal message As String = "")
    LogWrite "INFO", moduleName, "START", message
End Sub

Public Sub LogEnd(ByVal moduleName As String, Optional ByVal message As String = "")
    LogWrite "INFO", moduleName, "END", message
End Sub

Public Sub LogError(ByVal moduleName As String, ByVal eventName As String, ByVal errObj As ErrObject)
    Dim msg As String
    msg = "ErrNumber=" & errObj.Number & ", Description=" & errObj.Description
    LogWrite "ERROR", moduleName, eventName, msg
End Sub
VB

ここを一箇所にまとめておくと、「ログのフォーマットを変えたい」ときも ModLogger だけ直せば済みます。

ModStopWatch:処理時間計測の共通部品

処理時間計測も、専用モジュールに切り出します。
StopWatch クラスと組み合わせて使う形です。

' ModStopWatch.bas
Option Explicit

Public Function ElapsedSeconds(ByVal startTime As Double) As Double
    Dim nowT As Double
    nowT = Timer
    
    If nowT >= startTime Then
        ElapsedSeconds = nowT - startTime
    Else
        ElapsedSeconds = (86400# - startTime) + nowT
    End If
End Function
VB

StopWatch クラスは別途クラスモジュールに置きます(前回のテンプレの通り)。
モジュール構成として、「時間計測はここを見る」と決めておくのが大事です。

ModConfigUtil:設定シート系の小物を集約

列記号→列番号変換など、設定シート周りの小物関数はここに集めます。

' ModConfigUtil.bas
Option Explicit

Public Function ColToNumber(ByVal colRef As Variant) As Long
    If IsNumeric(colRef) Then
        ColToNumber = CLng(colRef)
    Else
        ColToNumber = Range(CStr(colRef) & "1").Column
    End If
End Function
VB

JOIN・集計・差分・変換の各エンジンから共通で使うことで、「列の解釈がバラバラ」という事故を防げます。


クラスモジュール:役割がはっきりした“部品”だけをクラス化する

どこまでクラスにするか

全部をクラスにしようとすると、逆に分かりづらくなります。
モジュール構成テンプレとしては、「役割がはっきりしていて、状態を持つものだけクラスにする」がちょうどいいです。

例えば、StopWatch は典型的です。

開始時刻と前ステップ時刻という“状態”を持つ。
Lap を呼ぶたびに内部状態が変わる。
TotalSeconds や GetLogText で“結果”を返す。

こういうものはクラスにするときれいに収まります。
逆に、単なるユーティリティ関数(列変換など)は標準モジュールで十分です。


例題:このモジュール構成テンプレを実務ブックに当てはめる

例えば、「顧客日次処理ブック」を作るとします。
モジュール構成はこうなります。

ModEntry
日次入口(RunDailyJobs)だけを書く。

ModBatchEngine
ConfigBatch を読み、ImportCustomer / ConvCustomer / JoinCustomer / AggCustomer を順に実行。

ModConvEngine
ConfigConv を読み、ノーコード変換ツールとして顧客整形を行う。

ModJoinEngine
ConfigJoin を読み、顧客×マスタJOINを行う。

ModAggEngine
ConfigAgg を読み、顧客集計を行う。

ModLogger
全マクロ共通のログ出力。

ModStopWatch + StopWatch クラス
重い処理の時間計測。

ModConfigUtil
列変換などの共通ユーティリティ。

業務マクロ(ImportCustomer / ConvCustomer / JoinCustomer / AggCustomer)は、
それぞれ「エンジン呼び出し+ログ+エラー処理」だけを書く。
こうしておくと、「どの処理を直したいか」に応じて、見るべきモジュールが自然に決まります。


まとめ:モジュール構成テンプレは「未来の自分への優しさ」

今日のポイントを一言で言うと、「役割ごとにモジュールを分け、名前と中身のルールを決める」です。

入口は ModEntry。
業務フローは ModBatchEngine。
JOIN・集計・差分・変換はそれぞれ専用モジュール。
ログ・時間計測・設定ユーティリティは共通基盤モジュール。
状態を持つ部品だけクラスモジュール。

この“型”を一度決めてしまえば、新しいブックを作るたびに
「今回も同じ構成でいこう」と迷わずに済みます。

もし、今あなたが持っているVBAプロジェクトのスクリーンショットやモジュール名一覧があれば、
それをこのテンプレにどう整理し直せるか、一緒に具体的に組み替えてみるのも面白いですよ。

タイトルとURLをコピーしました