【テクニカル・上級編】モジュール間での定数共有:Public Constの乱用を防ぐ「設定用クラス」の設計 – Excel VBA解析バイブル

スポンサーリンク

モジュール間での定数共有:Public Constの乱用を防ぐ「設定用クラス」の設計

VBAにおける大規模開発の現場において、最も看過されがだが致命的な技術的負債の一つが、標準モジュールにおける `Public Const` の乱用である。

「どこからでも参照できるから便利だ」という安易なアプローチは、アプリケーションの結合度を極限まで高め、保守性を破壊する。定数の値が変更された際の影響範囲はコードベース全体に及び、依存関係の追跡は不可能になる。さらに、VBAのコンパイル仕様上、標準モジュールのパブリック定数は暗黙のグローバル名前空間を汚染し、メモリの効率的な解放やコンポーネントのカプセル化を根本から阻害する。

本稿では、レガシーなVBAアーキテクチャの限界を突破し、オブジェクト指向の原則に基づいた「設定用クラス(Config Class)」による堅牢な定数管理手法を提示する。

1. なぜ `Public Const` の乱用は悪なのか

多くの開発者は、定数(Const)は変数値(Variable)とは異なり「書き換えられないのだから安全である」と誤認している。しかし、問題の本質は「値の書き換え可能性」ではなく、「名前空間の汚染」と「結合度(Coupling)」にある。

結合度の肥大化

標準モジュールに定義された `Public Const` は、プロジェクト内のどのプロシージャからでもプレフィックスなしで呼び出すことが可能になる。これは一見すると開発生産性を上げるように見えるが、実際には「どのモジュールがどの定数に依存しているか」の依存関係グラフを複雑怪奇なスパゲッティ状にする。ある定数の意味を変更、あるいは削除した際、影響を受けるモジュールを静的に解析する術はVBAの貧弱なIDEには存在しない。

メモリとライフサイクルの無視

VBAの標準モジュールは、プログラムの実行開始から終了までメモリ上に常駐し続ける。少量の定数であれば無視できるメモリフットプリントも、外部APIの定義やハードコードされた設定値が数千行に及ぶようになると、VBAの32bitアドレス空間(約2GBの壁)を確実に圧迫する。

これを解決唯一のアプローチが、「状態を持たない、あるいはカプセル化された読み取り専用プロパティを持つクラス(設定用クラス)」の導入である。

2. 設計思想:設定用クラス(`AppConfig`)のアーキテクチャ

目指すべき設計は、グローバル変数やパブリック定数を排除し、設定値を「オブジェクトのプロキシ」として提供することだ。

これにより、以下のメリットがもたらされる。
1. 名前空間の局所化: 設定値へのアクセスは、明示的にインスタンス化された(あるいはシングルトンとして管理される)クラス経由に限定される。
2. イミュータブル(不変)な公開: VBAにはC#のような `readonly` 修飾子や `Const` プロパティが存在しないが、「Property Get のみを提供し、Property Let/Set を実装しない」ことで、完全に書き込み不可能な読み取り専用プロパティをエミュレートできる。
3. 動的な値の注入(DIの萌芽): 将来的に設定値をハードコードされた定数から、外部INIファイル、レジストリ、あるいはデータベースからの動的ロードに変更する場合でも、呼び出し側のコードを一切書き換える必要がない(カプセル化の恩恵)。

3. 実装コード:堅牢な設定用クラスの構築

実際のVBAプロジェクトに即した実装を示す。ここでは、APIのタイムアウト時間、ファイルパス、および環境識別子を管理するクラスを構築する。

クラスモジュール:`AppConfig`

(※クラスモジュールの名称を `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 = True
Attribute VB_Exposed = False
Option Explicit

‘ =========================================================================
‘ クラス名: AppConfig
‘ 概要: アプリケーション全体で使用する設定値をカプセル化する読み取り専用クラス
‘ 備考: PredeclaredId = True により、インスタンスを明示的にNewすることなく
‘ 静的クラスのようにも扱えるが、依存性注入の観点からはインスタンス化を推奨。
‘ =========================================================================

‘ 内部保持用のプライベート定数(モジュール内カプセル化)
Private Const DEF_TIMEOUT_SEC As Long = 30
Private Const DEF_LOG_FILE_NAME As String = “app_execution.log”
Private Const DEF_API_ENDPOINT_PRD As String = “https://api.enterprise.internal/v1/”
Private Const DEF_API_ENDPOINT_DEV As String = “https://dev-api.enterprise.internal/v1/”

‘ 実行時環境判定用のプライベート変数
Private m_IsDevelopment As Boolean

‘ コンストラクタ(クラス初期化時に環境を判定)
Private Sub Class_Initialize()
‘ レジストリ、環境変数、または特定のシート状態から開発環境か判定するロジック
‘ ここでは簡略化のため、特定のファイルの存在有無で判定する例とする
m_IsDevelopment = CheckIsDevelopmentEnvironment()
End Sub

‘ — 読み取り専用プロパティ群 —

Public Property Get TimeoutSeconds() As Long
TimeoutSeconds = DEF_TIMEOUT_SEC
End Property

Public Property Get LogFileName() As String
LogFileName = DEF_LOG_FILE_NAME
End Property

Public Property Get ApiEndpoint() As String
If m_IsDevelopment Then
ApiEndpoint = DEF_API_ENDPOINT_DEV
Else
ApiEndpoint = DEF_API_ENDPOINT_PRD
End If
End Property

Public Property Get IsDevelopment() As Boolean
IsDevelopment = m_IsDevelopment
End Property

‘ — プライベートヘルパーメソッド —

Private Function CheckIsDevelopmentEnvironment() As Boolean
‘ 例: 開発者用のフラグファイルがローカルに存在するかどうかで判定
Dim fso As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ ※実際の運用では適切なパスに書き換えてください
CheckIsDevelopmentEnvironment = fso.FileExists(“C:\Temp\vba_debug_mode.flag”)

‘ オブジェクトの明示的解放によるメモリ最適化
Set fso = Nothing
End Function

4. 呼び出し側の実装とメモリ最適化

上記で作成した設定クラスを、実際の業務処理を行う標準モジュールからどのように呼び出すか。ここでもVBA特有のメモリ管理の罠を回避する作法が要求される。

標準モジュール:`MainModule`

Option Explicit

Sub ExecuteBusinessProcess()
‘ 設定クラスの参照変数
Dim config As AppConfig

On Error GoTo ErrorHandler

‘ インスタンスの生成(Newキーワードによる明示的ライフサイクル管理)
Set config = New AppConfig

‘ ログ出力の初期化(設定クラスから値を取得)
Debug.Print “— 処理開始 —”
Debug.Print “環境: ” & IIf(config.IsDevelopment, “開発環境”, “本番環境”)
Debug.Print “APIエンドポイント: ” & config.ApiEndpoint
Debug.Print “タイムアウト設定: ” & config.TimeoutSeconds & “秒”
Debug.Print “ログファイル: ” & config.LogFileName

‘ 実際のビジネスロジックをここで実行(設定オブジェクトを引数で渡すDIパターンも有効)
Call RunCoreLogic(config)

CleanUp:
‘ 【極限の知見】オブジェクトの明示的解放
‘ VBAのガベージコレクションは参照カウント方式であるため、
‘ スコープを抜ける前にNothing代入を行うことで即時メモリ解放を確実にする。
Set config = Nothing
Debug.Print “— 処理終了 —”
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical
Resume CleanUp
End Sub

Private Sub RunCoreLogic(ByRef cfg As AppConfig)
‘ 必要な設定値のみをインターフェース経由で受け取ることで疎結合を維持
‘ ここでグローバル定数に依存しないため、テスト容易性が飛躍的に向上する。

‘ 例: API呼び出しのシミュレーション
Dim timeout As Long
timeout = cfg.TimeoutSeconds

‘ 実際の処理…
End Sub

5. レガシー環境・システム間連携における実践的アドバイス

長年運用されてきた巨大なExcelマクロ資産を改修する際、すべての `Public Const` を一気にクラスへ置き換えることはリスクが高すぎる。以下のステップを踏むことで、安全かつ段階的なリファクタリングが可能となる。

1. アダプター層の設置:
既存の標準モジュール群が参照しているグローバル定数を無理に即時削除せず、一時的に `AppConfig` クラスへのプロキシ関数(標準モジュール内のラップ関数)を噛ませることで、コンパイルエラーを防ぎながら徐々に参照先をクラスへ移行する。
2. Windows APIとの統合:
もし定数がWindows APIの定数(例:`MB_OK`, `WM_COMMAND` など)である場合、これらをアプリケーション固有の設定クラスに混ぜるべきではない。API定数は専用の `ApiConstants` のような名前空間的モジュールに分離し、ビジネスロジック用の設定(`AppConfig`)とは厳格に分離すること。
3. パフォーマンスへの配慮:
クラスのインスタンス化コストはVBAにおいても極めて軽量であるが、もし毎秒何千回もループ内で `New AppConfig` を実行するような愚行を犯せば、COMのオーバヘッドによりパフォーマンスは致命的に低下する。設定クラスは、「プロセスライフサイクル(または処理のトップレベル)で一度だけインスタンス化し、必要なモジュールへ参照(ByRef)として渡す」のが鉄則である。

結論

`Public Const` の乱用は、コードの書きやすさと引き換えに、システムの拡張性と保守性を売り渡す悪魔の契約である。

「設定用クラス」の導入は、一見するとコード量が微増し、記述の手間が増えるように感じられるかもしれない。しかし、これこそがスパゲッティVBAを「保守可能なエンタープライズ・アプリケーション」へと昇華させる唯一の王道であり、シニアエンジニアが備えるべき不可欠なデザインパターンである。明日の保守性のため、今すぐグローバル定数の依存関係を断ち切れ。

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