モジュール間での定数共有:Public Constの乱用を防ぐ「設定用クラス」の設計
開発プロジェクトの初期段階ではうまく回っていたはずのVBAマクロが、コード量が増えるにつれて「どこで値が書き換わっているのか分からない」「設定値を変更しただけで予期せぬバグが発生する」といったスパゲッティコードと化していく——。
あなたも、そんな悪夢のような現場に直面したことはないだろうか。
その元凶の多くは、グローバル変数や `Public Const`(パブリック定数)の安易な乱用にある。
本稿では、VBAにおけるモジュール間でのデータ共有のアンチパターンを断ち切り、読み取り専用プロパティを持つ「設定用クラス(Config Class)」を用いた、堅牢かつ疎結合な設計手法を徹底解説する。現場の保守性を劇的に向上させるプロフェッショナルな知見を授けよう。
—
なぜ `Public Const` の乱用はエンジニアの首を絞めるのか?
標準モジュール(`.bas`)の先頭に、以下のようなコードを並べ立てていないだろうか。
‘ 【アンチパターンの例】標準モジュールでのPublic定義
Public Const API_TIMEOUT As Long = 30
Public Const OUTPUT_PATH As String = “C:\Data\Output\”
一見すると、どこからでもアクセスできて便利に見える。しかし、これには重大な設計上の欠陥がある。
1. 名前空間の汚染と依存関係の不可視化
どのプロシージャがどこでこの定数を参照しているのか、影響範囲が追えなくなる。依存関係がクモの巣状になり、リファクタリングが不可能になる。
2. 「定数」でありながら環境変化に弱い
`Const` はコンパイル時に値が固定される。もし「テスト環境と本番環境でファイルパスやAPIのURLを切り替えたい」となった場合、コードを書き換えて再コンパイルする必要が生じる。これはエンタープライズ開発において致命傷となる。
3. カプセル化の欠如
VBAでは `Public Const` は書き換えられないが、これが `Public` な変数(グローバル変数)に変わった瞬間、意図しないタイミングで値が書き換わるデバッグ地獄の完成である。
我々が目指すべきは、「変更に強く、誰がどこから触っても安全なコード」だ。それを実現するのが、クラスモジュールによる「設定の抽象化」である。
—
解決策:設定用クラス(`AppConfig`)の設計思想
オブジェクト指向の基本原則である「カプセル化」と「関心の分離」をVBAに持ち込む。
アイデアはシンプルだ。
- 設定値を保持するクラス(例: `AppConfig`)を一つ作成する。
- 外部からの値の書き込みを一切許さず、`Get` プロパティ(読み取り専用)のみを公開する。
- 値の初期化は、クラスのイニシャライズ時(または設定ファイル読み込み時)に一回だけ行う。
これにより、他のモジュールは「設定がどこから来るか」を意識する必要がなくなり、提供されたプロパティを安全に読み取るだけという、完全に疎結合な関係が構築できる。
—
実装コード:プロダクション品質の「設定用クラス」
ここからは、実務でそのまま使える堅牢なコードを提示する。
今回は、ハードコーディングを避け、将来的なINIファイルや外部データ連携への拡張も見据えた設計とする。
1. クラスモジュール:`AppConfig` の作成
VBAのプロジェクトにクラスモジュールを追加し、名前を `AppConfig` に変更する。
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 ‘True
END
Attribute VB_Name = “AppConfig”
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
” ====================================================================
” クラス名: AppConfig
” 概要: アプリケーション全体の設定値を管理する読み取り専用クラス
” ====================================================================
Option Explicit
‘ 内部保持用のプライベート変数
Private m_TimeoutSeconds As Long
Private m_OutputFolder As String
Private m_EnvironmentName As String
‘ コンストラクタの代わりとなる初期化メソッド
‘ 外部からの不正な再初期化を防ぐため、Friendスコープまたは内部制御を推奨
Friend Sub Initialize()
On Error GoTo ErrorHandler
‘ 【実務の知見】本来はここでINIファイルやDB、Workbook上の特定シートから読み込む
‘ 今回はサンプルとして、環境に応じた動的な設定をシミュレートする
m_EnvironmentName = “Production”
m_TimeoutSeconds = 45
‘ 実行ファイルのパスを基準にした動的なパス構築(ハードコーディングの排除)
m_OutputFolder = ThisWorkbook.Path & “\Output\”
‘ フォルダが存在しない場合は自動生成する自己防衛ロジック
If Dir(m_OutputFolder, vbDirectory) = “” Then
MkDir m_OutputFolder
End If
Exit Sub
ErrorHandler:
Err.Raise Err.Number, “AppConfig.Initialize”, “設定の初期化に失敗しました: ” & Err.Description
End Sub
‘ ——————————————————————–
‘ 読み取り専用プロパティ群
‘ ——————————————————————–
Public Property Get TimeoutSeconds() As Long
TimeoutSeconds = m_TimeoutSeconds
End Property
Public Property Get OutputFolder() As String
OutputFolder = m_OutputFolder
End Property
Public Property Get EnvironmentName() As String
EnvironmentName = m_EnvironmentName
End Property
2. 標準モジュールからの利用方法(メイン処理)
標準モジュールでは、`AppConfig` のインスタンスを生成し、必要な処理へ依存性として渡す(またはインスタンスを保持する)。これにより、グローバル汚染を完全に防ぐことができる。
Option Explicit
Sub Main_Process()
‘ 設定クラスのインスタンスを生成
Dim config As AppConfig
Set config = New AppConfig
‘ 初期化を実行(ここで設定値が確定する)
config.Initialize
‘ 業務ロジックを実行するモジュールへ設定オブジェクトを渡す
Call ExecuteBusinessLogic(config)
‘ クリーンアップ
Set config = Nothing
MsgBox “処理が正常に完了しました。”, vbInformation
End Sub
Sub ExecuteBusinessLogic(ByRef config As AppConfig)
‘ 【注目】ここではグローバル変数に一切依存せず、安全にプロパティを参照している
Debug.Print “— 処理開始 —”
Debug.Print “実行環境: ” & config.EnvironmentName
Debug.Print “タイムアウト設定: ” & config.TimeoutSeconds & “秒”
Debug.Print “出力先パス: ” & config.OutputFolder
‘ 実際のファイル出力処理やAPI通信処理がここに続く…
End Sub
—
発展:ファイル連携やデータベース連携への拡張性
実務において、設定値は「コードを変更せずに変えたい」という要件が必ず発生する。
今回の `AppConfig` 設計であれば、`Initialize` メソッドの中身を書き換えるだけで、外部ソースへの対応がシームレスに行える。
1. 設定用Excelシート(Configシート)から読み込ませる場合
ワークシートの特定セル(例: A列にキー、B列に値)から設定を読み込む場合も、クラスのインターフェース(プロパティ)を変える必要はない。呼び出し側は内部の変更を一切気にする必要がないのだ。
‘ AppConfig.Initialize 内の拡張例
Private Sub Initialize_FromSheet()
Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Sheets(“Config”)
On Error GoTo 0
If ws Is Nothing Then
‘ フォールバック(デフォルト値)の適用
m_TimeoutSeconds = 30
Exit Sub
End If
‘ シートから値を取得
m_TimeoutSeconds = CLng(ws.Range(“B1”).Value)
m_OutputFolder = CStr(ws.Range(“B2”).Value)
End Sub
この設計にしておけば、ユーザーが設定を変更したい場合でも、VBAのコードを触る必要がなくなり、Excelシート上のセルの値を書き換えるだけで対応可能になる。これこそが、保守性の高いプロダクションコードの姿である。
—
ティック、タック、と時は流れる。あなたの書くVBAコードは、一過性の「使い捨ての自動化スクリプト」で終わるべきではない。
組織の資産となり、後任のエンジニアから「おっ、分かっているな」と唸らせるような、堅牢で美しいアーキテクチャを構築してほしい。
`Public Const` の乱用を断ち切り、クラスによるカプセル化を今日からあなたの開発スタンダードに組み込もう。
