【実務・中級編】DAOのRecordsetを閉じ忘れない:クラスモジュールを活用した自動クローズ設計 – Access VBA解析バイブル

スポンサーリンク
スポンサーリンク

メモリリークとロックの連鎖を断ち切れ:Class_Terminateによる「自動クローズ」の極意

Access VBAで業務システムを構築していると、避けては通れない、そして最も厄介な問題に直面することがある。

「昨日まで動いていたツールが、なぜか急に『データベースがロックされています』と叫び出す」
「数時間動かしているだけで、Accessの動作が目に見えて重くなる」

もし君が、プロシージャの末尾に `rs.Close` と `Set rs = Nothing` を書くことで「後始末は完璧だ」と思っているなら、その認識は極めて危険だ。それは「正常系」における片手落ちの対策に過ぎない。

実務における真の敵は、予期せぬエラーによってコードの実行が中断された際に、「閉じられなかったRecordset」がメモリとデータベースエンジンの中に居座り続けることだ。これこそが、リソース枯渇とファイル破損の真犯人である。

今日は、オブジェクトのライフサイクルを完全に掌握し、バグの入り込む余地を物理的に排除する「クラスモジュールを活用した自動クローズ設計」を伝授する。

なぜ、君の「後始末」は失敗するのか

まず、一般的な「ダメな設計」の構造を理解しよう。

' --- 典型的な(しかし脆弱な)コード ---
Sub BadPractice()
    Dim db As DAO.Database
    Dim rs As DAO.Recordset
    
    Set db = CurrentDb
    Set rs = db.OpenRecordset("SELECT * FROM T_Sales", dbOpenDynaset)
    
    ' ここで何らかのロジックを実行
    ' もし、ここで予期せぬエラーが発生したら……?
    DoSomethingComplex rs 
    
    ' エラーが発生すると、ここには到達しない
    rs.Close
    Set rs = Nothing
    Set db = Nothing
End Sub

このコードの致命的な欠陥は、`DoSomethingComplex` の中でエラーが発生した瞬間、`rs.Close` を実行する権利をプログラムが失うことだ。
エラーハンドリング(`On Error GoTo`)を書けば防げると思うかもしれない。だが、複雑なシステムになればなるほど、エラーハンドリングの書き漏らしや、条件分岐によるクローズ漏れは必ず発生する。

エンジニアの精神力に依存する設計は、プロフェッショナルの仕事ではない。「忘れても、仕組みが強制的に片付ける」。この設計思想こそが、堅牢なシステムを作る鍵だ。

解決策:RAII(Resource Acquisition Is Initialization)の概念をVBAへ

C++などの高度な言語には、RAIIという設計思想がある。「オブジェクトの生成時にリソースを取得し、オブジェクトの破棄時にリソースを解放する」という考え方だ。

これをVBAで実現するために、クラスモジュールを利用する。クラスのインスタンスがメモリから消滅する際、必ず呼ばれる `Class_Terminate` イベントを「強制的な清掃員」として活用するのだ。

1. クラスモジュールの実装:`clsSafeRecordset`

まず、クラスモジュールを挿入し、名前を `clsSafeRecordset` と変更せよ。このクラスは、Recordsetを包み込み(ラップし)、その寿命を管理する役割を担う。

Option Explicit

' 内部的にRecordsetを保持するプライベート変数
Private m_rs As Object ' DAO/ADOどちらにも対応できるようObject型とする

''' <summary>
''' Recordsetを初期化し、管理下に置く
''' </summary>
''' <param name="targetRs">管理対象のRecordsetオブジェクト</param>
Public Sub Initialize(ByRef targetRs As Object)
    Set m_rs = targetRs
End Sub

''' <summary>
''' Recordset内のデータを操作するためのプロパティ(ラッパー)
''' </summary>
Public Property Get Recordset() As Object
    Set Recordset = m_rs
End Property

''' <summary>
''' クラスが破棄されるときに自動的に実行される(最重要)
''' </summary>
Private Sub Class_Terminate()
    ' オブジェクトが存在し、かつ閉じられていない場合にのみクローズを実行
    If Not m_rs Is Nothing Then
        On Error Resume Next ' クローズ時のエラーでプロセスを止めないための防護策
        If m_rs.State = 1 Then ' ADOの場合のチェック(DAOの場合は適宜調整)
            m_rs.Close
        Else
            ' DAOの場合、Stateプロパティがないため、
            ' 実行環境に応じてエラーハンドリングで制御
            m_rs.Close
        End If
        Set m_rs = Nothing
        On Error GoTo 0
    End If
End Sub

2. 標準モジュールでの実戦的な使い方

次に、このクラスを実際にどう使うか。ポイントは、Recordsetを直接変数として持つのではなく、「クラスのインスタンス」として保持することだ。

Option Explicit

Sub RobustDataProcess()
    Dim db As DAO.Database
    ' Recordsetそのものではなく、管理用クラスを宣言する
    Dim safeRs As clsSafeRecordset
    
    Set db = CurrentDb
    Set safeRs = New clsSafeRecordset
    
    On Error GoTo ErrorHandler
    
    ' 1. Recordsetを開き、クラスに「委譲」する
    ' これにより、safeRsの寿命がRecordsetの寿命を決定する
    safeRs.Initialize db.OpenRecordset("SELECT * FROM T_LargeData", dbOpenDynaset)
    
    ' 2. データ処理
    ' safeRs.Recordset を通じて操作する
    Do While Not safeRs.Recordset.EOF
        Debug.Print safeRs.Recordset!FieldName
        
        ' --- ここで仮にエラーが発生したとしよう ---
        ' Err.Raise 999, , "意図的なエラー発生" 
        
        safeRs.Recordset.MoveNext
    Loop

CleanUp:
    ' 正常終了時も、明示的にNothingを代入して破棄を促す
    Set safeRs = Nothing
    Set db = Nothing
    Exit Sub

ErrorHandler:
    MsgBox "エラーが発生しました: " & Err.Description, vbCritical
    ' エラーが発生しても、safeRsをNothingにするだけで良い
    ' Class_Terminateが走り、Recordsetは確実に閉じられる
    Resume CleanUp
End Sub

アーキテクトの視点:なぜこれが「最強」なのか

この設計には、3つの決定的なメリットがある。

① エラーハンドリングの簡素化

従来の設計では、`ErrorHandler` の中で「どのRecordsetが開いたままか」をすべて把握し、個別に `Close` を書く必要があった。しかし、この設計なら `Set safeRs = Nothing` を一行書くだけで、内部のRecordsetも、その先のメモリも、すべてが連鎖的に、かつ確実に解放される。

② トランザクションの整合性

Accessで最も恐ろしいのは、Recordsetが開きっぱなしの状態でトランザクション(`BeginTrans`)を完了(`CommitTrans`)できず、データベースが「ゾンビ状態」になることだ。自動クローズ設計は、この「ロックの残存」を構造的に防ぐ。

③ メンテナンスコストの激減

新しい開発者がチームに加わったとき、彼らが `Close` を書き忘れるかもしれない。しかし、このクラスを使っている限り、彼らがミスをしてもシステム全体が崩壊することはない。「個人のスキルに依存しない、仕組みによる品質保証」。これこそが、大規模な業務ツールに求められる設計だ。

まとめ:設計の重みを知れ

プログラミングにおける「動くコード」と「堅牢なコード」の差は、「例外的な事態(エラーや中断)をいかにエレガントに処理するか」という一点に集約される。

Recordsetをただの変数として扱うのは、素人の仕事だ。オブジェクトには必ず「誕生」と「死」がある。そのライフサイクルをクラスで包み込み、設計レベルで制御する。この一歩を踏み出せるかどうかが、一流のエンジニアへの分かれ道である。

今日から君のコードから、`rs.Close` の書き漏らしという概念を消し去れ。

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