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

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

Access VBAを掌握する:Recordsetの「閉じ忘れ」をゼロにする、クラスモジュールによる自動クローズ設計

こんにちは。現場のアーキテクチャ設計を担当している、あなたの「頼れる先輩エンジニア」です。

Access VBAを学び始め、マクロの記録から一歩踏み出して「自分でコードを書けるようになった!」という時期は、本当に楽しいものです。しかし、ある程度の規模のツールを作り始めると、誰もが一度はぶつかる「見えない壁」があります。

それは、「なぜかファイルがロックされて開けない」「動作がどんどん重くなっていく」「エラーが出た瞬間に、メモリが食いつぶされるような感覚がある」といった、リソース管理の問題です。

今日は、プログラミング初学者がプロのエンジニアへと脱皮するために避けては通れない、「オブジェクトのライフサイクル(寿命)」という概念と、それをスマートに制御する「クラスモジュールを活用した自動クローズ設計」について、魂を込めて解説します。

ここをクリアすれば、あなたのAccess開発の質は劇的に向上します。準備はいいですか?

1. なぜ「Recordsetの閉じ忘れ」は致命的なのか?

VBAでデータを扱う際、必ずと言っていいほど登場するのが `DAO.Recordset` です。データベースからデータを取り出し、中身を読み取るための「窓口」のようなものです。

通常、私たちは以下のような手順でコードを書きます。

' --- 典型的な(しかし危うい)書き方 ---
Sub BadExample()
    Dim db As DAO.Database
    Dim rs As DAO.Recordset
    
    Set db = CurrentDb
    Set rs = db.OpenRecordset("SELECT * FROM T_売上データ", dbOpenSnapshot)
    
    ' --- 何か複雑な処理 ---
    ' ここでエラーが発生したとしましょう...
    
    rs.Close ' ← エラーが起きると、ここには到達しません!
    Set rs = Nothing
    Set db = Nothing
End Sub

ここに潜む「罠」

上記のコードで、もし「複雑な処理」の途中でエラーが発生したらどうなるでしょうか?

VBAのエラーハンドリング(`On Error GoTo …`)が働くと、プログラムの実行はエラー処理用のラベルへジャンプします。その結果、`rs.Close` という「後片付け」の行がスキップされてしまうのです。

Recordsetが閉じられないまま放置されると、以下のような不都合が起こります:
1. メモリの浪費: 閉じられていないオブジェクトがメモリ上に残り続け、Accessが重くなります。
2. ファイルのロック: 他のユーザーやプロセスがそのテーブルを編集できなくなることがあります。
3. デバッグの困難化: 「さっきまで動いていたのに、なぜかエラーが出る」という原因不明の挙動に悩まされます。

「手動で閉じる」という作業は、人間がミスをする前提の設計なのです。

2. 解決策:オブジェクトに「自分の後始末」を任せる

プロの設計思想は違います。「人間が閉じ忘れるなら、オブジェクト自身に、自分が死ぬ時に勝手に片付けさせればいい」と考えます。

これを実現するのが、クラスモジュールの `Class_Terminate` イベントです。

クラスモジュールとは?

一言で言えば、「独自の道具(オブジェクト)を作るための設計図」です。
クラスモジュールには、そのオブジェクトが消滅する瞬間に自動的に実行される「葬儀(後片付け)」の儀式を書き込んでおくことができます。

3. 実践:Recordsetを自動管理する「ラッパー・クラス」の構築

それでは、実際に「Recordsetを自動で閉じてくれる魔法のクラス」を作っていきましょう。

ステップ1:クラスモジュールの作成

VBAエディタのメニューから [挿入] -> [クラスモジュール] を選択し、プロパティウィンドウでオブジェクト名を `clsRecordset` に変更してください。

そこに、以下のコードを貼り付けます。

' ==================================================
' クラス名: clsRecordset
' 目的: DAO.Recordsetのライフサイクルを自動管理する
' ==================================================
Option Explicit

' 内部だけで保持するプライベートな変数
Private pRs As DAO.Recordset

''' <summary>
''' Recordsetを開くメソッド
''' </summary>
''' <param name="db">対象のデータベース</param>
''' <param name="sql">実行するSQL文</param>
Public Sub Open(ByRef db As DAO.Database, ByVal sql As String)
    ' すでに開いている場合は一度閉じる(安全策)
    If Not pRs Is Nothing Then
        pRs.Close
        Set pRs = Nothing
    End If
    
    ' Recordsetを開く
    Set pRs = db.OpenRecordset(sql, dbOpenSnapshot)
End Sub

''' <summary>
''' 外部からRecordset本体にアクセスするためのプロパティ
''' </summary>
Public Property Get Recordset() As DAO.Recordset
    Set Recordset = pRs
End Property

''' <summary>
''' 【最重要】クラスが破棄される時に自動で呼ばれるイベント
''' </summary>
Private Sub Class_Terminate()
    ' もしRecordsetが開いたままなら、ここで確実に閉じる
    If Not pRs Is Nothing Then
        On Error Resume Next ' 閉じるときのエラーは無視して完遂させる
        pRs.Close
        Set pRs = Nothing
        On Error GoTo 0
    End If
    ' ここで、このクラス自身がメモリから解放される
End Sub

ステップ2:標準モジュールでの使用方法

次に、このクラスを実際に使う側のコードを書きます。

' ==================================================
' 標準モジュール
' ==================================================
Option Explicit

Sub ProfessionalExample()
    Dim db As DAO.Database
    ' クラス型の変数を宣言
    Dim myRsWrapper As clsRecordset
    
    Set db = CurrentDb
    
    ' クラスのインスタンス化(実体を作る)
    Set myRsWrapper = New clsRecordset
    
    ' クラスを通じてRecordsetを開く
    myRsWrapper.Open db, "SELECT * FROM T_売上データ"
    
    ' --- データの利用 ---
    ' Property Get を通じて Recordset を操作する
    Do Until myRsWrapper.Recordset.EOF
        Debug.Print myRsWrapper.Recordset!売上金額
        myRsWrapper.Recordset.MoveNext
    Loop
    
    ' --- ここでエラーが発生したとしても、大丈夫! ---
    ' Err.Raise 18 Error ' テスト用にエラーを発生させてみてください
    
    ' 処理が終わったら、クラスの変数をNothingにする
    ' これだけで、Class_Terminate が走り、Recordsetも自動で閉じられる!
    Set myRsWrapper = Nothing
    
    MsgBox "処理が正常に完了し、リソースも解放されました。", vbInformation
End Sub

4. なぜこれで「安全」になるのか?(アーキテクチャの解説)

ここが、初級者と上級者の分かれ道です。図解的に理解しましょう。

通常の設計(手動管理)

1. `rs` を作成
2. `rs` を使う
3. [エラー発生!] $\rightarrow$ `rs.Close` を通り過ぎる $\rightarrow$ `rs` がメモリに幽霊のように残る(リーク)

クラスを用いた設計(自動管理)

1. `myRsWrapper`(クラス)を作成 $\rightarrow$ その中に `pRs`(Recordset)を格納
2. `myRsWrapper` を使う
3. [エラー発生!] $\rightarrow$ プロシージャが終了 $\rightarrow$ 変数 `myRsWrapper` の寿命が尽きる $\rightarrow$ `Class_Terminate` が自動発動! $\rightarrow$ `pRs.Close` が確実に実行される

このように、「変数のスコープ(有効範囲)が切れる = オブジェクトが死ぬ = 自動的に後片付けが走る」という仕組みを利用しているのです。

たとえあなたがコードの途中でエラーに遭遇しても、VBAの仕組みが「あ、この変数はもう使わないんだな」と判断した瞬間に、クラスが責任を持って掃除をしてくれます。

まとめ:ここをクリアすれば、プロの入り口です

「Recordsetを閉じる」という、一見すると単純で些細な作業。しかし、その背後には「リソースのライフサイクルをいかに制御するか」という、ソフトウェアエンジニアリングにおける極めて重要なテーマが隠されています。

1. 人間を信じない: エラー時に `Close` を忘れるのは必ず起こる。
2. オブジェクトに責任を持たせる: クラスの `Class_Terminate` を活用する。
3. 設計の抽象度を上げる: 「Recordsetを直接扱う」のではなく「Recordsetを管理するクラスを扱う」という考え方を持つ。

この「クラスによるカプセル化」の考え方が身につけば、あなたの作るAccessツールは、どんなに複雑になっても、驚くほど安定した堅牢なものになります。

一歩ずつ、着実に。この知見をあなたの武器にしてください。応援していますよ!

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