オブジェクト変数の初期化忘れを防ぐ:クラスの Initialize イベント活用法
こんにちは。チーフアーキテクトの私だ。
これまで数々の巨大なExcel業務自動化ツール、いわゆる「VBAモンスターシステム」のコードレビューを行ってきた。その中で、幾度となく目にしてきた悪夢のようなバグがある。
それが、「オブジェクト変数の初期化忘れ(実行時エラー 91: オブジェクト変数または With ブロック変数が見つかりません)」だ。
‘ ありがちな悲劇のコード
Dim db As clsDatabase
‘ Set db = New clsDatabase ‘ ←これを書き忘れた!
db.Connect ‘ ここで無慈悲なエラー 91 が発生し、ユーザーの作業データが吹き飛ぶ
開発者自身がテストしている時は `New` を書き忘れない。しかし、現場の担当者に引き渡し、運用が始まってから「特定のエッジケースでこのエラーが出る」という報告が上がる。この絶望感は何とも言えないものだ。
プログラミング初心者であれば「気をつければいい」で済ませるかもしれない。しかし、プロのエンジニアは「人間の注意力に依存した設計は、必ず破綻する」という前提でシステムを組む。
今回は、クラスの `Class_Initialize` イベントを極限まで活用し、「インスタンス化された瞬間から100%安全に稼働する」堅牢なクラス設計の極意を伝授しよう。
—
なぜ「使う側のNew」に依存してはいけないのか?
VBAで独自のクラス (`cls` モジュール) を設計する際、私たちはついつい以下のようなコードを書きがちだ。
‘ 呼び出し側 (Standard Module)
Sub Main()
Dim config As clsConfig
Set config = New clsConfig
‘ プロパティに値をセットしていく
config.FilePath = “C:\Data\settings.json”
config.Load
End Sub
このアプローチの何が問題か?
それは、「このクラスを使うプログラマ(あるいは未来の自分)が、正しくプロパティを設定し、初期化メソッドを呼び出す手順を知っていること」を強制している点にある。
もし、ある開発者が `config.Load` を呼び忘れたらどうなるか? 内部のファイルパスは空のままで動き、予期せぬファイルI/Oエラーを引き起こす。
解決策:クラス自身に「生まれた瞬間」の責任を持たせる
オブジェクト指向の基本に立ち返ろう。オブジェクトとは、データと振る舞いをカプセル化した「自律した存在」であるべきだ。
「生まれ落ちた瞬間(`New`された瞬間)から、不完全な状態で存在することを許さない」。これを実現するのが `Class_Initialize` イベントだ。
—
実践:ファイル・DB連携を例にした堅牢なクラス設計
ここでは、実務で最も需要が高い「外部設定ファイル(またはDB接続)を読み込み、安全に保持するクラス」を例に、プロダクションコードを示す。
1. クラスモジュール:`clsAppConfig` の実装
VBAのクラスモジュールに `clsAppConfig` と名前をつけ、以下のコードを貼り付けてほしい。
Option Explicit
‘ 内部保持用プライベート変数
private m_IsInitialized As Boolean
private m_ConnectionString As String
private m_TimeoutSeconds As Long
‘ 読み取り専用プロパティ(外部から勝手に書き換えられないようにする)
Public Property Get IsInitialized() As Boolean
IsInitialized = m_IsInitialized
End Property
Public Property Get ConnectionString() As String
‘ 未初期化状態でのアクセスをブロック
If Not m_IsInitialized Then
Err.Raise 9999, “clsAppConfig”, “致命的エラー: クラスが正しく初期化されていません。”
End If
ConnectionString = m_ConnectionString
End Property
Public Property Get TimeoutSeconds() As Long
TimeoutSeconds = m_TimeoutSeconds
End Property
‘ ==========================================================
‘ コンストラクタ (Class_Initialize)
‘ ==========================================================
Private Sub Class_Initialize()
‘ 1. デフォルト値の安全なフォールバック設定
m_TimeoutSeconds = 30
m_IsInitialized = False
‘ 2. 自動初期化プロセスの実行(レジストリ、INIファイル、DBからの自動ロード)
On Error GoTo ErrorHandler
‘ 例:ここで環境に応じた設定を自動読み込みする
Call LoadConfigurationsFromFile
m_IsInitialized = True
Exit Sub
ErrorHandler:
‘ 初期化失敗時はフラグを落としたままにする
m_IsInitialized = False
Debug.Print “[Critical] clsAppConfig の初期化に失敗しました: ” & Err.Description
‘ 必要に応じてログ出力やエラー伝播を行う
End Sub
‘ ==========================================================
‘ 内部初期化ロジック(プライベートメソッド)
‘ ==========================================================
private Sub LoadConfigurationsFromFile()
‘ 実務を想定したダミー処理:本来はここでテキストファイルやDBから設定を取得する
‘ 例として固定値をセット
m_ConnectionString = “Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Production\Master.accdb;”
‘ 万が一設定ファイルが存在しない場合のガード
‘ If Dir(“C:\config.ini”) = “” Then Err.Raise 53, , “設定ファイルが見つかりません”
End Sub
‘ ==========================================================
‘ デストラクタ (Class_Terminate)
‘ ==========================================================
Private Sub Class_Terminate()
‘ リソースの解放(ファイルハンドル、DBコネクションのクローズなど)
Debug.Print “clsAppConfig は正常に破棄されました。”
End Sub
この設計の圧倒的なアドバンテージ
1. 「呼ぶ順番を間違えようがない」
呼び出し側は、ただ `New` するだけで、勝手にファイル読み込みとデフォルト値のフォールバックが完了している。
2. 「不正な状態でのアクセスを物理的に遮断」
万が一初期化に失敗した場合や、手順が抜けた場合でも、`Property Get` 内の `If Not m_IsInitialized Then` ガードが作動し、即座に明確なエラー(Err.Raise)を投げる。原因不明のバグに悩まされる時間がゼロになる。
3. 「デストラクタ (`Class_Terminate`) による後始末の自動化」
オブジェクトがスコープを抜ける、あるいは `Set obj = Nothing` された瞬間にメモリやリソースの解放処理を走らせられる。メモリリークやファイルのロック残りを完璧に防ぐ。
—
呼び出し側のコード(標準モジュール)
このクラスを使う側のコードは、驚くほどシンプルになる。余計な初期化メソッドの呼び出しは一切不要だ。
Sub ExecuteBusinessProcess()
‘ 宣言と同時に(あるいは直後に)インスタンス化
Dim config As clsAppConfig
Set config = New clsAppConfig
‘ すでに初期化済みなのを確認して安全に利用
If config.IsInitialized Then
MsgBox “接続文字列: ” & config.ConnectionString, vbInformation, “初期化成功”
Else
MsgBox “システムの初期化に失敗しました。管理者に連絡してください。”, vbCritical
End If
‘ スコープを抜ける、またはここで解放
Set config = Nothing
End Sub
—
チーフアーキテクトからの実務アドバイス
VBAは、他のモダン言語(C#やJavaなど)に比べてオブジェクト指向のサポートが「おっとり」している。そのため、ついつい手続き型のベタ書きコード(標準モジュールにプロシージャを並べるスタイル)に逃げがちだ。
しかし、業務自動化ツールが大規模化・複雑化したとき、命を救うのは「堅牢なカプセル化」と「ライフサイクルの自動管理」に他ならない。
- `New` した瞬間に何をやらせるべきか?
- インスタンスが消滅する間際に何を片付けるべきか?
この2点を `Class_Initialize` と `Class_Terminate` に的確にデザインし込むだけで、あなたの書くVBAコードの信頼性はプロのプロダクションレベルへと劇的に進化する。
「人がうっかりミスをする前提」のコードから、「システム自体が誤用を許さない」高次元の設計へ。
今日の業務から、ぜひこのパターンを導入してみてほしい。
