Excel VBA 逆引き集 | ロジック分離

Excel VBA
スポンサーリンク

ねらい:ロジック分離で「壊れにくく、直しやすい」VBAへ

Excel VBAは「セル操作」と「ビジネス処理」が混ざりやすく、修正が難しくなりがちです。ロジック分離とは、画面(UI)・データアクセス(シート/ファイル)・業務ロジック(計算/判定)・設定(Config)・共通基盤(開始/終了/ログ/進捗)を分ける設計のこと。初心者でも貼って使えるテンプレを用意し、重要部分を深掘りして解説します。

  • 目的: 変更の影響を局所化し、テスト可能で再利用しやすいコードを作る
  • 基本方針: 「業務ロジックはセルを知らない」「UIはロジックを呼ぶだけ」に徹する
  • 重要ポイント(深掘り):
    • 分層化が命: 読む・計算する・書くを別モジュールに分ける
    • 純粋関数化: 入力→出力だけの関数にするとテスト・再利用が超簡単
    • 例外と後片付け: どこで失敗しても「環境復帰」できる共通フレームを用意

ロジック分離の原則(短く強く)

  • UIとロジックを分ける:
    • UIは見せるだけ: ボタン押下、メッセージ、進捗表示
    • ロジックは計算だけ: セル・Range・Workbookを知らない関数
  • データアクセスを分ける:
    • Reader/Writer: シートから配列に読み、配列からシートに書く
  • 設定を分ける:
    • Configシート: 変更はシート編集、コードは読むだけ
  • 共通基盤を用意:
    • 開始/終了/ログ/進捗/停止: すべての処理で同じフレームを再利用
  • 深掘り(重要):
    • 依存の向き: UI→サービス(業務)→ロジック(純粋)/逆は無し
    • 副作用の封じ込め: セル書き換えは「Writer」だけに集約し、他は触らない

プロジェクト構成テンプレ

  • ModApp(共通基盤): 開始/終了/例外、最適化スイッチ
  • ModConfig(設定): Config読み取りヘルパー
  • ModIO(データアクセス): シート→配列、配列→シート
  • ModLogic(業務ロジック): 計算・判定の純粋関数
  • ModService(サービス): IOとLogicを組み合わせる業務ユースケース
  • ModUI(入口): ボタン/メニュー/メッセージ/進捗
  • Optional(UserFormProgress): プログレスバー、停止ボタン
  • 深掘り(重要):
    • 変更の場所が明確: シート列変わった→ModIOだけ直す、判定ルール変わった→ModLogicだけ直す
    • テストしやすい: ModLogicは単体テスト可能(配列や値を渡すだけ)

共通基盤(開始・終了・例外)テンプレ

' ModApp
Option Explicit

Public Sub AppEnter(Optional ByVal msg As String = "")
    Application.ScreenUpdating = False
    Application.EnableEvents = False
    Application.Calculation = xlCalculationManual
    If Len(msg) > 0 Then Application.StatusBar = msg
End Sub

Public Sub AppLeave(Optional ByVal clearStatusBar As Boolean = True)
    Application.Calculation = xlCalculationAutomatic
    Application.EnableEvents = True
    Application.ScreenUpdating = True
    If clearStatusBar Then Application.StatusBar = False
End Sub
VB
  • 深掘り(重要):
    • Finally思想: 「どこで落ちても」戻せる枠があると安心
    • 速度と安定: 画面更新OFF、イベントOFFでブレを減らす

設定の分離(Config読み取り)

' ModConfig
Option Explicit

Private Function ConfigSheet() As Worksheet
    Set ConfigSheet = ThisWorkbook.Worksheets("Config")
End Function

Public Function GetConfigString(ByVal key As String) As String
    Dim ws As Worksheet: Set ws = ConfigSheet()
    Dim lastRow As Long: lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
    Dim r As Long
    For r = 2 To lastRow
        If StrComp(CStr(ws.Cells(r, "A").Value), key, vbTextCompare) = 0 Then
            GetConfigString = Trim$(CStr(ws.Cells(r, "B").Value))
            Exit Function
        End If
    Next
    Err.Raise 701, , "Configキーが見つかりません: " & key
End Function
VB
  • 深掘り(重要):
    • 定数禁止: 文字列・フォルダ・閾値はConfigから読む前提に
    • 誤設定検知: 見つからなければ即エラー。黙ったまま進めない

データアクセスの分離(Reader/Writer)

' ModIO
Option Explicit

Public Function ReadInput(ByVal sheetName As String) As Variant
    Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets(sheetName)
    Dim lastRow As Long: lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
    Dim lastCol As Long: lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
    If lastRow < 2 Then Err.Raise 710, , "入力が空です: " & sheetName
    ReadInput = ws.Range(ws.Cells(1, 1), ws.Cells(lastRow, lastCol)).Value ' 2次元配列
End Function

Public Sub WriteOutput(ByVal sheetName As String, ByVal destTopLeft As String, ByVal data As Variant)
    Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets(sheetName)
    Dim rows As Long: rows = UBound(data, 1)
    Dim cols As Long: cols = UBound(data, 2)
    ws.Range(destTopLeft).Resize(rows, cols).Value = data
End Sub
VB
  • 深掘り(重要):
    • セル非依存のI/O API: ロジックは配列を受け取り、配列を返すだけ
    • 範囲の決定はIO側: 列追加・順序変更はIOで吸収

業務ロジックの分離(純粋関数)

例:入力データから「合格判定」列を追加するロジック。セルを触らない。

' ModLogic
Option Explicit

' data: 1行目ヘッダー、2行目以降データ
' threshold: 合格点
Public Function AddPassFlag(ByVal data As Variant, ByVal threshold As Double) As Variant
    Dim rows As Long: rows = UBound(data, 1)
    Dim cols As Long: cols = UBound(data, 2)
    Dim out() As Variant: ReDim out(1 To rows, 1 To cols + 1)
    
    Dim r As Long, c As Long
    ' コピー + 新ヘッダー
    For c = 1 To cols: out(1, c) = data(1, c): Next
    out(1, cols + 1) = "合格"
    
    ' 判定(例:点数がE列=5列目)
    For r = 2 To rows
        For c = 1 To cols: out(r, c) = data(r, c): Next
        out(r, cols + 1) = IIf(Val(data(r, 5)) >= threshold, "○", "×")
    Next
    
    AddPassFlag = out
End Function
VB
  • 深掘り(重要):
    • 副作用ゼロ: 読み・書きはしない。入力→出力のみ
    • テスト容易: 単体テストで配列を渡して結果を確認できる

サービス層(IO×Logicの組み合わせ)

' ModService
Option Explicit

Public Sub Run_AddPassFlag()
    On Error GoTo ErrHandler
    AppEnter "合格判定"
    
    Dim inputSheet As String: inputSheet = GetConfigString("INPUT_SHEET")   ' 例: "Input"
    Dim outputSheet As String: outputSheet = GetConfigString("OUTPUT_SHEET") ' 例: "Output"
    Dim threshold As Double: threshold = Val(GetConfigString("THRESHOLD_SCORE"))
    
    Dim data As Variant: data = ReadInput(inputSheet)
    Dim out As Variant: out = AddPassFlag(data, threshold)
    Call WriteOutput(outputSheet, "A1", out)
    
    AppLeave True
    MsgBox "合格判定を出力しました。"
    Exit Sub
ErrHandler:
    AppLeave True
    MsgBox "失敗: " & Err.Description, vbExclamation
End Sub
VB
  • 深掘り(重要):
    • 依存の流れが綺麗: Config→IO→Logic→IO。UIはここを呼ぶだけ
    • 差し替え容易: 列位置・判定ルールが変わっても、直す場所が特定できる

UI層(ボタン・メニュー・完了メッセージ)

' ModUI
Option Explicit

Public Sub Btn_Run()
    Call Run_AddPassFlag
End Sub
VB
  • 深掘り(重要):
    • UIは薄く: 入口だけ。メッセージや進捗表示をここで足す
    • 再利用: 同じロジックを複数UI(ボタン/メニュー/ショートカット)から呼べる

単体テストのやり方(ロジックが分離されているから簡単)

Sub Test_AddPassFlag()
    Dim data(1 To 3, 1 To 5) As Variant
    ' ヘッダー
    data(1, 1) = "社員番号": data(1, 2) = "氏名": data(1, 3) = "部署": data(1, 4) = "日付": data(1, 5) = "点数"
    ' データ
    data(2, 1) = "000001": data(2, 5) = 80
    data(3, 1) = "000002": data(3, 5) = 60
    
    Dim out As Variant: out = AddPassFlag(data, 70)
    Debug.Print out(1, 6)  ' 合格
    Debug.Print out(2, 6)  ' ○
    Debug.Print out(3, 6)  ' ×
End Sub
VB
  • 深掘り(重要):
    • Excel不要で検証: シート操作なし。即座に結果を確認
    • 回帰テスト: 仕様変更時にテストで壊れを検出できる

例題で練習(貼って試せる)

  • 例1(分離体験): Inputに「点数」を用意→Btn_RunでOutputに「合格」列が追加される。
  • 例2(Config変更): THRESHOLD_SCOREを60に変更→コード無改修で結果が変わる。
  • 例3(列変更): 点数列をE→Fへ移動した場合、ModLogicの参照列だけ直す。
  • 例4(単体テスト): Test_AddPassFlagで配列テストを実行、結果が期待通りか確認。

実務の落とし穴と対策(ここが肝)

  • 落とし穴1:ロジックがセル参照だらけ
    • 対策: IOで配列化→ロジックは配列のみ扱う。セル参照はWriterに限定。
  • 落とし穴2:例外時に環境が戻らない
    • 対策: AppEnter/AppLeaveのFinallyを徹底。すべての入口で使う。
  • 落とし穴3:責務が曖昧になり再び混ざる
    • 対策: モジュールごとに「して良いこと」を決め、違反をレビューで防ぐ。
  • 落とし穴4:Configを読まずに定数埋め込み
    • 対策: 文字列・パス・閾値は必ずGetConfigXXX経由に統一。
  • 落とし穴5:テストがない
    • 対策: ModLogicに「純粋関数」を増やし、テストSubをセットで書く。

スターター手順(最短導入)

  1. モジュールを分ける: ModApp/ModConfig/ModIO/ModLogic/ModService/ModUIを作成。
  2. IOとロジックを分離: まず「配列に読み→配列で処理→配列を書き戻す」形に変換。
  3. Config化: シート名・フォルダ・閾値はConfigから読むヘルパーを導入。
  4. テストSubを用意: ロジックの純粋関数に対して配列テストを作る。
  5. 入口統一: すべての処理でAppEnter/AppLeave+例外ハンドリングを徹底。

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