リソース解放の美学:DAO Recordsetの「メモリリーク」を根絶するアーキテクチャ設計
業務システムを構築する際、多くの開発者が「ロジックの正しさ」にのみ執着し、致命的な落とし穴を見落としている。それが「オブジェクトのライフサイクル管理」だ。
「動いているから問題ない」という言葉は、プロフェッショナルにとっては敗北宣言に等しい。特に、Access VBAにおいてDAO(Data Access Objects)を扱う際、適切にリソースを解放しないコードは、長時間稼働させた際にメモリを食いつぶし、最終的にはAccessそのものをクラッシュさせる「時限爆弾」となる。
今日は、設計の甘さが招くメモリリークのメカニズムを解剖し、プロダクション環境に耐えうる「堅牢なオブジェクト破棄の作法」を伝授する。
なぜ「Set Nothing」だけでは不十分なのか
初心者はよくこう言う。「最後に `Set rs = Nothing` と書いているから大丈夫です」と。
断言しよう。それは極めて危険な誤解だ。
VBAにおけるオブジェクトは、COM(Component Object Model)という仕組みに基づいた「参照カウント」によって管理されている。オブジェクトがメモリ上に留まるのは、そのオブジェクトを指し示している変数が1つ以上存在する場合だ。
問題は、「エラーが発生したとき」に起こる。
破綻するコードの典型例
以下のコードを見てほしい。一見、正しくオブジェクトを解放しているように見える。
' ❌ 脆弱な設計:エラー時にリソースがリークする
Sub BadExample()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set db = CurrentDb
Set rs = db.OpenRecordset("SELECT * FROM HeavyTable", dbOpenSnapshot)
' --- 何らかの複雑なロジック ---
' ここで予期せぬエラー(型不一致、データ欠損など)が発生すると...
Dim x As Integer
x = 1 / 0 ' ゼロ除算エラー発生!
' ---------------------------
rs.Close
Set rs = Nothing ' ← ここに到達せず、rsはメモリ上に「幽霊」として残り続ける
db.Close
Set db = Nothing
End Sub
エラーが発生した瞬間、プログラムの実行権限は `Err_Handler`(エラーハンドラ)へジャンプする。その結果、`rs.Close` や `Set rs = Nothing` という「解放の儀式」はスキップされる。
この「スキップされたオブジェクト」は、参照が残ったままメモリを占有し続ける。これが積み重なることで、メモリリークが発生し、システムは死に至るのだ。
プロフェッショナルが採用する「クリーンアップ・パターン」
堅牢なシステムを設計するアーキテクトは、「正常終了時」と「異常終了時」の出口を一本化する。 これを「クリーンアップ・パターン」と呼ぶ。
設計の鉄則はこうだ:
1. エラーが発生しても、必ず「解放処理専用のセクション」を通るように設計せよ。
2. `Resume CleanUp` を使い、エラーハンドラから解放処理へ合流せよ。
完璧な実装テンプレート
以下に、実務でそのまま利用できる、極めて堅牢な構造を示す。これをテンプレートとして脳に刻んでほしい。
''' <summary>
''' 業務データ処理の堅牢なテンプレート
''' </summary>
Public Sub RobustDataProcessing()
' 1. オブジェクト変数の宣言(初期値はNothingであることが保証される)
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strSql As String
' エラーハンドリングの設定
On Error GoTo Err_Handler
' 2. オブジェクトの初期化
Set db = CurrentDb
strSql = "SELECT * FROM T_Transaction WHERE Status = 'Pending'"
Set rs = db.OpenRecordset(strSql, dbOpenSnapshot)
' 3. メインロジック
' ---------------------------------------------------------
Do Until rs.EOF
' ここで複雑な計算や外部API連携を行う
' 万が一ここでエラーが起きても、確実にクリーンアップへ向かう
Debug.Print rs!TransactionID
rs.MoveNext
Loop
' ---------------------------------------------------------
' --- 正常終了時もここを通る ---
CleanUp:
' 4. オブジェクトの破棄(逆順で解放するのが鉄則)
' エラーが発生していても、ここへ Resume CleanUp によって到達する
On Error Resume Next ' 解放処理中のエラーによる二次災害を防ぐ
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
If Not db Is Nothing Then
' CurrentDbを使用している場合はClose不要だが、
' 外部DB接続の場合は必ずCloseが必要
Set db = Nothing
End If
Exit Sub
' 5. エラーハンドリング
Err_Handler:
' エラーログの出力(実務ではログファイルやテーブルへ記録せよ)
MsgBox "致命的なエラーが発生しました。" & vbCrLf & _
"Error No: " & Err.Number & vbCrLf & _
"Description: " & Err.Description, vbCritical, "System Error"
' 重要:エラーを報告した後、必ずクリーンアップ・セクションへ合流させる
Resume CleanUp
End Sub
アーキテクトの視点:さらなる高みへ
このパターンをマスターした君に、さらに踏み込んだ知見を授けよう。
1. 解放の「逆順」原則
オブジェクトのライフサイクルには依存関係がある。`Recordset` は `Database` に依存している。したがって、破棄する際は必ず 「子(Recordset)から先に、親(Database)を後から」 解放せよ。親を先に `Nothing` にしてしまうと、子の解放プロセスが宙に浮き、制御不能なメモリリークを招く可能性がある。
2. `On Error Resume Next` の正しい使い方
上記の `CleanUp` セクション内で `On Error Resume Next` を使用していることに気づいたか?
これは「サボり」ではない。「解放処理そのものが失敗したときに、エラーハンドラが無限ループしたり、エラーを握りつぶしてプロセスを停止させたりするのを防ぐ」 ための、極めて理にかなった防衛策だ。
3. DAOとADOの境界線
もし君が、外部のSQL ServerへADO(ActiveX Data Objects)で接続する場合、管理の重要性はさらに跳ね上がる。ADOの `Connection` オブジェクトは、ネットワークリソースやサーバー側のセッションを直接占有する。DAOのメモリリークは「ローカルのクラッシュ」で済むが、ADOの接続リークは「サーバー側のセッション枯渇」を招き、全社的なシステムダウンを引き起こす。
結論
「動くコード」を書くのは初級者だ。「壊れないコード」を書くのが中級者。そして、「リソースの行方まで完全に制御下に置いたコード」を書くのが、我々プロフェッショナルだ。
メモリリークを防ぐことは、単なるテクニックではない。それは、ユーザーが安心して使い続けられる「信頼」を設計することと同義である。
次にコードを書くときは、自分に問いかけてほしい。
「もしここでエラーが起きたら、このオブジェクトは成仏できるか?」 と。
