クラスモジュールにおけるPrivate変数のカプセル化とPropertyプロシージャの極限統御
VBAは、そのアクセスの手軽さゆえに「場当たり的なコード」の温床になりやすい。特に、グローバル変数やパブリック変数がモジュール間で乱れ飛び、どのタイミングで値が書き換わったのか追跡できないスパゲッティコードのデバッグに、どれほどのエンジニアが人生の時間を溶かしてきたことか。
真に堅牢なエンタープライズVBAアプリケーションを構築するためには、オブジェクト指向の基本原則である「カプセル化」をクラスモジュールによって徹底し、データの整合性を担保しなければならない。
今回は、クラス内部の`Private`変数を守護し、不正な値の流入を完全に遮断するための `Property Get` / `Let` / `Set` の実装パターンと、メモリのライフサイクルまで踏み込んだ極限の知見を公開する。
—
1. なぜ「Public変数」の直叩きがシステムを崩壊させるのか
初学者は、クラスモジュール内に `Public Name As String` のように変数を定義しがちだ。これでは単なる「名前付きの構造体」であり、クラスとしてのカプセル化の恩恵はゼロに等しい。
外部から無制限に値が代入可能であるということは、以下のような致命的なリスクを孕む。
- 不正な型の流入や範囲外の値(Null、空白、オーバーフローする数値)の混入
- 値が変更された瞬間のフック(イベント発火やログ出力、関連する内部状態の再計算)の欠如
- マルチインスタンス環境における競合や状態汚染
ビジネスロジックとデータ構造を分離し、データへのアクセスを厳格に制御するための門番こそが Propertyプロシージャ である。
—
2. Property Get / Let / Set の正確な使い分けと実装哲学
VBAにおけるPropertyプロシージャには、対象のデータ型によって明確な使い分けが存在する。ここを曖昧にしているプログラマは、VBAのメモリモデルを理解していないと言わざるを得ない。
- `Property Get`: 内部のPrivate変数を外部に公開(参照)する。
- `Property Let`: プリミティブ型(Long, String, Boolean, Date等)の値を代入する。
- `Property Set`: オブジェクト参照(Worksheet, Collection, 自作のクラスインスタンス等)を代入する。
実装サンプル:堅牢なエンタープライズ・コンフィグクラス
以下のコードは、設定値を保持しつつ、不正な空文字の代入を防ぎ、さらに値の変更を検知するロジックを組み込んだクラスモジュールの実装例である。
‘ =================================ライブラリ/クラス名: CAppConfig =================================
Option Explicit
‘ 【カプセル化】外部から直接触らせないPrivate変数
Private m_ConfigName As String
Private m_TimeoutSeconds As Long
Private m_LogTargetSheet As Worksheet ‘ オブジェクト参照用
‘ イベント定義(値が変更された瞬間に外へ通知する場合など)
‘ Event ConfigChanged(ByVal propertyName As String)
Private Sub Class_Initialize()
‘ デフォルト値の初期化
m_ConfigName = “DefaultEnvironment”
m_TimeoutSeconds = 30
Set m_LogTargetSheet = Nothing
Debug.Print “CAppConfig: Instance Initialized.”
End Sub
Private Sub Class_Terminate()
‘ デストラクタ:オブジェクト参照の確実な解放
Set m_LogTargetSheet = Nothing
Debug.Print “CAppConfig: Instance Terminated.”
End Sub
‘ — 1. String型のプロパティ (Property Let) —
Public Property Get ConfigName() As String
ConfigName = m_ConfigName
End Property
Public Property Let ConfigName(ByVal RHS As String)
‘ 入力値検証(バリデーション)
If Trim(RHS) = “” Then
Err.Raise 50001, “CAppConfig”, “ConfigNameには空文字を指定できません。”
End If
m_ConfigName = Trim(RHS)
‘ RaiseEvent ConfigChanged(“ConfigName”) ‘ 必要に応じてイベント発火
End Property
‘ — 2. 数値型のプロパティ (Property Let) と範囲チェック —
Public Property Get TimeoutSeconds() As Long
TimeoutSeconds = m_TimeoutSeconds
End Property
Public Property Let TimeoutSeconds(ByVal RHS As Long)
‘ 範囲外の値の弾薬
If RHS < 1 Or RHS > 300 Then
Err.Raise 50002, “CAppConfig”, “TimeoutSecondsは1から300の範囲で指定してください。”
End If
m_TimeoutSeconds = RHS
End Property
‘ — 3. オブジェクト型のプロパティ (Property Set) —
Public Property Get LogTargetSheet() As Worksheet
‘ 参照を返すときはそのまま返す(必要ならカプセル化の観点から複製を検討)
Set LogTargetSheet = m_LogTargetSheet
End Property
Public Property Set LogTargetSheet(ByVal RHS As Worksheet)
‘ オブジェクトがNothingでないこと、あるいは特定のシート名かどうかの検証
If RHS Is Nothing Then
Err.Raise 50003, “CAppConfig”, “有効なWorksheetオブジェクトを指定してください。”
End If
‘ 参照の代入には必ず Set を使用する
Set m_LogTargetSheet = RHS
End Property
—
ジニアスな視点を持つ者であれば、ここで一つの疑問が浮かぶはずだ。「なぜ `Property Let` の引数名が `RHS` なのか?」 と。
`RHS` とは Right Hand Side(右辺) の略称であり、VB系言語の歴史的背景から受け継がれる慣習だ。代入式の右側に入る値であることを明示している。保守性を極限まで高めるコードベースでは、こうした細部へのこだわりがチーム全体のコード品質を引き上げる。
—
3. メモリのライフサイクルと参照循環(Circular Reference)の罠
クラスモジュールを多用するアーキテクチャにおいて、最も恐ろしいのは「メモリリーク」である。VBAのガベージコレクションは参照カウント方式(Reference Counting)を採用している。
ここで `Property Set` を用いてオブジェクトを相互参照させると、循環参照(Circular Reference)が発生し、インスタンスが `Class_Terminate` を迎えることなくメモリ上に永遠に残り続けるという致命的なバグが生まれる。
対策:明示的な参照の切断(Nullification)
親クラスと子クラスが相互にポインタを持ち合うような設計は極力避けるべきだが、やむを得ない場合は、処理の完了時に明示的にインスタンスのリンクを断ち切るメソッドを実装する必要がある。
‘ クラス破棄用の明示的メソッド(Disposeパターン)
Public Sub Dispose()
‘ 内部保持しているオブジェクト参照を強制的に解放
Set m_LogTargetSheet = Nothing
‘ さらに他のカスタムクラスを持っている場合もここで連鎖的に解放する
‘ If Not m_SubModule Is Nothing Then
‘ m_SubModule.Dispose
‘ Set m_SubModule = Nothing
‘ End If
End Sub
呼び出し側(Standard Moduleなど)でも、使い終わったインスタンスは必ず `Nothing` を代入してメモリからパージする鉄則を忘れてはならない。
Sub MainProcess()
Dim config As CAppConfig
Set config = New CAppConfig
On Error GoTo ErrorHandler
‘ プロパティを通じた安全な操作
config.ConfigName = “ProductionEnvironment”
config.TimeoutSeconds = 60
Set config.LogTargetSheet = ThisWorkbook.Sheets(“Log”)
‘ — ビジネスロジックの実行 —
‘ 終了処理
config.Dispose
Set config = Nothing
Exit Sub
ErrorHandler:
MsgBox “エラー発生: ” & Err.Description, vbCritical
If Not config Is Nothing Then
config.Dispose
Set config = Nothing
End If
End Sub
—
4. レガシー環境・API連携を見据えた応用知見
大規模なExcelVBAシステムでは、外部のWindows APIやCOMコンポーネント、あるいは外部データベース(ADODBなど)と連携するケースが多い。こうしたコンテキストにおいて、Propertyプロシージャによるカプセル化は「型安全の担保」として極めて強力に機能する。
例えば、Windows APIのハンドルやメモリポインタをクラスでラップする場合、生の `LongPtr` 変数をそのままパブリックにすることは、メモリ破壊や不正メモリアクセス違反(Crash)への直行便となる。
APIの戻り値や入力値を `Property` の中で厳密にチェック・ラップし、VBAの安全なデータ型へと変換して外側に提供する。これが、レガシーとモダンを繋ぐプロフェッショナルのアプローチである。
—
総括
VBAだからといって、場当たり的なコードで妥協してはならない。
`Private` 変数による完全なカプセル化と、意図を明確にした `Property Get/Let/Set` の実装は、あなたの書くVBAコードを「単なるマクロ」から「堅牢なソフトウェア」へと昇華させる唯一の道である。
オブジェクトの生成から消滅(ライフサイクル)までを完全に支配し、保守性の極限に達したコードベースを構築してほしい。
