定数管理の外部化:JSON設定ファイルとVBAによる環境依存の完全排除
VBA(Visual Basic for Applications)のコード内に、接続文字列、APIエンドポイント、タイムアウト値、あるいは出力先のパスなどをハードコーディングする悪習は、今すぐ断ち切るべきだ。
開発環境、ステージング環境、本番環境。それぞれの環境で値が変わるたびにソースコードを修正し、.xlsmをリビジョン管理して配布し直す――これは運用地獄への片道切符に他ならない。真にスケーラブルで保守性の高いVBAシステムを構築するためには、「実行時設定の外部化」が不可欠となる。
本稿では、VBAネイティブが持たないJSONパーサーの壁を乗り越え、Windows標準機能(またはスクリプトエンジン)を極限まで活用して外部JSONから設定値を読み込み、メモリ上に構造体として安全に保持するアーキテクチャを解説する。
—
1. なぜ「JSONの外部化」と「メモリ上での保持」なのか
VBAにおける設定ファイルの読み込みといえば、レガシーなINIファイル(`GetPrivateProfileString` API)や、シート上にセルを定義して読み込ませる手法が主流だった。しかし、これらは構造化データの表現力に欠け、リレーショナルな設定や配列を扱うには限界がある。
現代のシステム間連携において、JSON(JavaScript Object Notation)はデファクトスタンダードである。これをVBAで扱う意義は以下の通りだ。
- 環境依存の完全排除: コードのコンパイル(VBAの場合は実行時コンパイル)を伴わずに挙動を制御できる。
- 型安全な構造化データの保持: 単なるキーバリューの羅列ではなく、階層構造を持った設定をメモリ上に展開できる。
- ライフサイクルの分離: 設定の変更がビジネスロジックに影響を与えない疎結合な設計を実現する。
しかし、VBAには標準でJSONをパースする機能がない。外部の巨大なライブラリに頼る手もあるが、セキュリティ制約が厳しい企業環境や、単体の配布が求められるマクロ有効ブックにおいて、外部依存関係(DLLの登録など)は持ち込みたくないのが実情だ。
そこで、Windows標準コンポーネント(JScript / ScriptControl または MSXML2)を巧みに利用し、依存関係ゼロでJSONを安全にメモリへロードする設計解を提示する。
—
2. 実装アーキテクチャの全体像
今回構築するアーキテクチャのコンポーネントは以下の3つである。
1. `config.json`: アプリケーションのルートと同じ階層に置かれる設定ファイル。
2. `Settings.cls` (クラスモジュール): 読み込んだJSONデータをカプセル化し、型安全なプロパティとして提供する構造体としての役割を担うモジュール。
3. `ConfigLoader.bas` (標準モジュール): ファイルシステムからJSONを読み込み、パーサーを介してクラスにマッピングするエントリーポイント。
—
3. 実装コード:極限まで洗練された設定管理モジュール
以下のコードは、エラーハンドリング、メモリの明示的解放、そしてモダンなVBAの作法を網羅した実用コードである。
① 設定保持用クラスモジュール: `Settings.cls`
まずは、JSONの構造を受け受け、VBA側で型安全にアクセスするためのプロパティを持つクラスを作成する。
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 ‘True
Persistable = 0 ‘False
DataBindingBehavior = 0 ‘False
ManagedBlob = 0 ‘False
CodingState = 0 ‘False
GlobalNameSpace = 0 ‘False
PredeclaredId = 0 ‘False
Exposed = 0 ‘False
END
Option Explicit
‘ =========================================================================
‘ クラス名: Settings
‘ 概要: 外部JSONから読み込んだ設定値を保持する構造体クラス
‘ =========================================================================
‘ プライベートフィールド(カプセル化の徹底)
Private m_Environment As String
Private m_ApiEndpoint As String
Private m_TimeoutSeconds As Long
Private m_LogOutputDirectory As String
‘ プロパティ定義(Read-Onlyとして外部公開)
Public Property Get Environment() As String
Environment = m_Environment
End Property
Public Property Get ApiEndpoint() As String
ApiEndpoint = m_ApiEndpoint
End Property
Public Property Get TimeoutSeconds() As Long
TimeoutSeconds = m_TimeoutSeconds
End Property
Public Property Get LogOutputDirectory() As String
LogOutputDirectory = m_LogOutputDirectory
End Property
‘ 内部からフィールドを設定するためのメソッド(Friendスコープによりカプセル化を維持)
Friend Sub Initialize(ByVal env As String, ByVal endpoint As String, ByVal timeout As Long, ByVal logDir As String)
m_Environment = env
m_ApiEndpoint = endpoint
m_TimeoutSeconds = timeout
m_LogOutputDirectory = logDir
End Sub
② 読み込み・パース・メモリ管理を担う標準モジュール: `ConfigManager.bas`
外部のJSONファイルを読み込み、JScriptエンジンを用いてオブジェクト化、最終的に上記のクラスインスタンスへ流し込む。
Option Explicit
‘ グローバルまたはアプリケーションライフサイクルで保持するインスタンス
‘ ※必要に応じてパブリック変数やシングルトンパターンで保持する
Private g_AppSettings As Settings
”’
”’
Public Function GetConfig() As Settings
If g_AppSettings Nothing Then
‘ 初回アクセス時、または明示的リロード時に読み込みを実行
Set g_AppSettings = LoadConfiguration(“config.json”)
End If
Set GetConfig = g_AppSettings
End Function
”’
”’
”’ 設定ファイル名(相対パスまたは絶対パス)
Private Function LoadConfiguration(ByVal FileName As String) As Settings
Dim filePath As String
filePath = ResolvePath(FileName)
‘ ファイル存在チェック
If Not FileExists(filePath) Then
Err.Raise vbObjectError + 1001, “ConfigManager”, “設定ファイルが見つかりません: ” & filePath
End If
Dim jsonString As String
jsonString = ReadTextFile(filePath)
‘ JScriptのevalを利用して安全かつ高速にJSONをオブジェクトに変換
Dim jsonObject As Object
Set jsonObject = ParseJsonToScriptObj(jsonString)
‘ 構造体クラスへマッピング
Dim settingsInstance As New Settings
On Error GoTo ErrorHandler
settingsInstance.Initialize _
CStr(jsonObject.environment), _
CStr(jsonObject.apiEndpoint), _
CLng(jsonObject.timeoutSeconds), _
CStr(jsonObject.logOutputDirectory)
Set LoadConfiguration = settingsInstance
‘ オブジェクトの明示的解放(メモリリーク防止)
Set jsonObject = Nothing
Exit Function
ErrorHandler:
Set jsonObject = Nothing
Err.Raise vbObjectError + 1002, “ConfigManager”, “JSONのパースまたはマッピングに失敗しました。構造を確認してください。” & vbCrLf & Err.Description
End Function
”’
”’
Private Function ParseJsonToScriptObj(ByVal json As String) As Object
Dim scriptEngine As Object
Set scriptEngine = CreateObject(“MSScriptControl.ScriptControl”)
scriptEngine.Language = “JScript”
scriptEngine.AddCode “function parseJSON(jsonStr) { return eval(‘(‘ + jsonStr + ‘)’); }”
Set ParseJsonToScriptObj = scriptEngine.Run(“parseJSON”, json)
‘ COMオブジェクトの参照を確実に破棄
Set scriptEngine = Nothing
End Function
”’
”’
Private Function ResolvePath(ByVal FileName As String) As String
If InStr(FileName, “:”) > 0 Or Left(FileName, 2) = “\\” Then
‘ すでに絶対パスまたはUNCパスの場合
ResolvePath = FileName
Else
‘ ブックと同じ階層を基準にする
ResolvePath = ThisWorkbook.Path & IIf(Right(ThisWorkbook.Path, 1) = “\”, “”, “\”) & FileName
End If
End Function
”’
”’
Private Function FileExists(ByVal path As String) As Boolean
Dim fso As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)
FileExists = fso.FileExists(path)
Set fso = Nothing
End Function
”’
”’
Private Function ReadTextFile(ByVal path As String) As String
Dim stream As Object
Set stream = CreateObject(“ADODB.Stream”)
With stream
.Type = 2 ‘ adTypeText
.Charset = “UTF-8”
.Open
.LoadFromFile path
ReadTextFile = .ReadText
.Close
End With
Set stream = Nothing
End Function
—
4. 実際のJSON設定ファイル: `config.json`
プロジェクトのルートディレクトリ(`ThisWorkbook.Path`)に以下の内容で `config.json` を配置する。
{
“environment”: “Production”,
“apiEndpoint”: “https://api.enterprise-system.internal/v1/data”,
“timeoutSeconds”: 30,
“logOutputDirectory”: “C:\\Logs\\VBA_App\\”
}
—
5. 業務コードからの呼び出し方法
実際のビジネスロジック層では、以下のように極めてクリーンなコードで設定値にアクセスできる。ハードコードされたマジックナンバーやURLは一切存在しない。
Sub ExecuteBusinessLogic()
‘ 設定値の取得(遅延ローディング&メモリキャッシュ)
Dim config As Settings
Set config = GetConfig()
‘ デバッグ出力やログ記録
Debug.Print “実行環境: ” & config.Environment
Debug.Print “接続先: ” & config.ApiEndpoint
Debug.Print “タイムアウト: ” & config.TimeoutSeconds & “秒”
‘ ここに実際のAPI通信やファイル処理を記述する
‘ 参照の解放
Set config = Nothing
End Sub
—
6. シニアエンジニアが押さえるべきアーキテクチャ上の注意点
メモリ管理とCOMオブジェクトのライフサイクル
VBAにおける `CreateObject` によるCOMコンポーネント(`ADODB.Stream` や `MSScriptControl.ScriptControl`)の生成は、スコープを抜けた時点で自動解放されるが、複雑な参照ループや長時間のバッチ処理においてはメモリリークの原因になり得る。
本実装では、各関数の最後で明示的に `Set xxx = Nothing` を実行し、参照カウンタを即座にデクリメントする設計としている。
セキュリティとエラーハンドリング
`MSScriptControl.ScriptControl` は強力だが、信頼性の低い外部JSONや悪意あるデータが混入した場合のインジェクションリスクを考慮する必要がある。社内の管理されたパスに配置された設定ファイル、かつ文字コード(UTF-8)の保証された環境下でのみ利用すること。より厳格なセキュリティが求められる環境では、MSXML2.DOMDocumentや正規表現による自前パーサーへの切り替えも視野に入れるべきである。
結言
VBAだからといって、保守性の低いコードを許容する言い訳にはならない。設定をコードから完全に切り離し、外部JSONとして駆動させるこのパターンを導入することで、あなたの開発するVBAシステムは「使い捨てのマクロ」から、「堅牢なエンタープライズ・アプリケーション」へと劇的に進化する。
構造を整え、依存を断ち、コードの美しさと強靭さを死守せよ。
