プロシージャ終了時の自動クリーンアップ:クラスモジュールのTerminateイベントを活用した堅牢なリソース管理術
開発現場で、こんなコードを見たことはないだろうか。
Sub DoSomething()
Dim cn As Object
Set cn = CreateObject(“ADODB.Connection”)
cn.Open “…”
‘ ── 何らかの処理 ──
If ws.Range(“A1”).Value = “” Then Exit Sub ‘ ← ここで抜けると…?
cn.Close
Set cn = Nothing
End Sub
愚劣な設計だ。途中で `Exit Sub` が実行されたり、想定外のランタイムエラーが発生したりした瞬間、`cn.Close` はバイパスされ、データベースのコネクションは宙ぶらりんになる。Excelのプロセスが背後でメモリリークを起こし、ファイル共有違反を引き起こす原因の典型例だ。
手動でエラーハンドラを作り込み、`Err_Handler:` ラベルに飛ばして後始末を書く? そんなボイラープレートコード(冗長な定型コード)を毎度書くのは、プログラマーの怠慢であり、バグの温床でしかない。
真に堅牢な業務自動化ツールを設計する者であれば、「リソースの解放は、言語のスコープとライフサイクルに完全に委ねる」べきだ。今回は、VBAにおけるクラスモジュールの `Terminate` イベントを駆使し、プロシージャ終了時に100%確実にクリーンアップを行うプロフェッショナルな設計手法を伝授する。
—
1. なぜ「手動のクリーンアップ」は破綻するのか
VBAの実行環境(VBAランタイム)は、ガベージコレクション(GC)の挙動が.NET等のモダン言語に比べて非常にプリミティブだ。参照カウント方式を採用しているが、開発者が明示的に `Set = Nothing` を書き忘れたり、予期せぬエラーで処理が中断したりすると、オブジェクトの参照がメモリ上に残り続ける。
特に実務で頻出する以下のリソースは、解放漏れが致命傷になる。
- ADO(Database Connection / Recordset): RDBへのコネクションが開きっぱなしになり、サーバー側のセッションを圧迫・ロックする。
- FileSystemObject (FSO): ファイルストリームの解放漏れによる「アクセス権限エラー」。
- 外部アプリケーション連携(Word / Outlook / IE等): バックグラウンドプロセスがタスクマネージャーに残留し、PCのメモリを食いつぶす。
「エラーが起きようが、途中で抜なかろうが、スコープを抜けた瞬間に自動で片付く仕組み」——これをVBAで実現するのが、クラスモジュールのスコープ管理である。
—
2. アーキテクチャの核心:クラスのスコープと `Terminate` イベント
VBAの標準モジュール(`Sub` や `Function`)内の変数は、そのプロシージャが終われば破棄される。しかし、オブジェクト変数が指し示す実体(インスタンス)のライフサイクルは、変数のスコープと完全に連動しているわけではない。
ここでクラスモジュールを使う。クラスのインスタンスをプロシージャ内のローカル変数として宣言した場合、その変数がスコープから外れる(プロシージャが終了する)と、VBAランタイムは自動的にそのクラスの `Class_Terminate` イベントを発火させる。
この「インスタンス消滅の確実性」を利用し、「クラスのコンストラクタ(`Initialize`)でリソースを獲得し、デストラクタ(`Terminate`)で確実に解放する」というRAII(Resource Acquisition Is Initialization)イディオムをVBA上に実装するのだ。
—
3. 実装:堅牢なADOコネクション管理クラス
百聞は一見に如かず。実務の現場でそのまま使える、データベース接続を安全に担保するクラスモジュールの設計コードを提示する。
クラスモジュール名:`clsDbConnection`
Option Explicit
‘ ── プライベートフィールド ──
Private m_Connection As Object
Private m_IsConnected As Boolean
‘ コンストラクタ:インスタンス生成時に自動実行
Private Sub Class_Initialize()
Set m_Connection = CreateObject(“ADODB.Connection”)
m_IsConnected = False
‘ Debug.Print “clsDbConnection: Initialized”
End Sub
‘ デストラクタ:スコープアウト時に100%確実に実行される
Private Sub Class_Terminate()
Call Me.Disconnect
Set m_Connection = Nothing
‘ Debug.Print “clsDbConnection: Terminated & Released”
End Sub
‘ 接続メソッド
Public Sub Connect(ByVal connectionString As String)
On Error GoTo ErrorHandler
If m_IsConnected Then Exit Sub
m_Connection.Open connectionString
m_IsConnected = True
Exit Sub
ErrorHandler:
Err.Raise Err.Number, “clsDbConnection.Connect”, “データベース接続に失敗しました: ” & Err.Description
End Sub
‘ 切断メソッド(外部からの明示的呼び出し、またはTerminateから自動呼び出し)
Public Sub Disconnect()
On Error Resume Next
If Not m_Connection Is Nothing Then
If m_Connection.State = 1 Then ‘ adStateOpen
m_Connection.Close
End If
End If
m_IsConnected = False
On Error GoTo 0
End Sub
‘ コネクションオブジェクトを返すプロパティ
Public Property Get Connection() As Object
Set Connection = m_Connection
End Property
呼び出し側(標準モジュール)
Sub ExecuteBusinessLogic()
‘ スコープの開始と共にクラスを生成
Dim db As clsDbConnection
Set db = New clsDbConnection
On Error GoTo ErrorHandler
‘ 接続(内部でエラーが起きても安全)
db.Connect “Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Data\Master.accdb;”
‘ ── 業務処理のシミュレーション ──
MsgBox “データベース処理を実行中…”, vbInformation
‘ 途中で抜ける条件分岐があったとしても…
If ActiveSheet.Range(“A1”).Value = “ABORT” Then
MsgBox “処理を中断します”, vbExclamation
Exit Sub ‘ ← ここで抜けても、この瞬間に db の Terminate が走る!
End Sub
‘ 正常終了
Exit Sub
ErrorHandler:
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical
‘- —————————————————-
‘ 【極限の知見】
‘ ここに 「db.Disconnect」 を書く必要は一切ない。
‘ プロシージャが終了する(Exit Sub, End Sub, エラー脱出含む)
‘ その瞬間、ローカル変数 `db` はスコープを失い破棄される。
‘ 同時に clsDbConnection の Terminate イベントが発火し、
‘ 自動的にコネクションが切断・解放される。
‘ —————————————————-
End Sub
—
4. この設計がもたらす圧倒的なメリット
1. 「解放忘れ」というヒューマンエラーの完全撲滅
開発者が `Disconnect` や `Set = Nothing` を書き忘れる余地を与えない。言語の仕組みに後始末を強制させるため、コードレビューでリソースリークの指摘をする必要すらなくなる。
2. エラーハンドリングのシンプル化
エラー時に「どこまでリソースを開いたか」を追跡して個別に閉じる複雑な分岐(スパゲッティ・エラーハンドラ)を書く必要がなくなる。例外が発生してスタックが巻き戻される際にも、ローカル変数のデストラクタは確実に呼ばれる。
3. 保守性の飛躍的向上
新しいプロシージャでデータベースを使いたい場合、開発者は `Dim db As New clsDbConnection` と書いてメソッドを叩くだけでいい。クリーンアップのロジックを意識する必要すらなくなる。
—
チーフアーキテクトからの最後のアドバイス
「動けばいい」というレベルのコードと、プロダクション環境で何年もの間、無人稼働に耐えうる堅牢なツールとの違いは、こうした「リソースのライフサイクルに対するシビアな思想」にある。
VBAは簡易的な言語学たれど、書く人間の設計思想次第で、エンタープライズレベルの堅牢性を持たせることは十分に可能だ。オブジェクトの誕生から消滅までのライフサイクルを完全にコントロールし、バグの入り込む隙間を архитекチャ(アーキテクチャ)の段階で塞ぐこと。それこそが、プロの業務自動化エンジニアの仕事である。
