Word VBAの深淵:表の変更履歴を追跡する「監査アーキテクチャ」の設計思想
Wordにおける表(Table)操作は、単なるドキュメント作成の域を超え、時に「非構造化データベースの墓場」と化す。多くのエンジニアが`Cell(r, c).Range.Text`を漫然と叩き、`Application.ScreenUpdating`で誤魔化す程度の最適化で満足している中、我々のようなアーキテクトは、その裏にあるオブジェクトのライフサイクルとメモリ管理の限界点を見極めなければならない。
今回は、コンプライアンス要求が厳しい現場で必須となる「Word表の変更履歴監査ツール」の極限設計を伝授する。
—
1. なぜ「Range」のキャッシュが命なのか
Wordのオブジェクトモデルは、一度アクセスするごとにドキュメント全体の再レイアウト計算を誘発する可能性がある。ループ内で `Table.Cell(i, j).Range` を安易に呼び出すことは、メモリ空間上で無駄なRangeオブジェクトのインスタンスを乱造し、GC(ガベージコレクション)に頼れないVBAのランタイムを疲弊させる。
アーキテクトの定石:オブジェクトの解放と再利用
‘ メモリの断片化を防ぐための参照保持と明示的な解放
Sub AuditTableChanges(ByVal targetTable As Table)
Dim cellRange As Range
Dim i As Long, j As Long
‘ ScreenUpdatingの停止は基本中の基本だが、真の最適化は
‘ ドキュメントの再計算を抑制する「UndoRecord」の活用にある
For i = 1 To targetTable.Rows.Count
For j = 1 To targetTable.Columns.Count
Set cellRange = targetTable.Cell(i, j).Range
‘ 処理後、即座にNothingを代入し、参照カウンタを操作する
‘ これを行わないと、巨大な表ではVBAのスタックが肥大化する
ProcessCellAudit cellRange, i, j
Set cellRange = Nothing
Next j
Next i
End Sub
—
2. 変更検知のロジック:ハッシュか、それともプロパティか
表の変更を検知する際、単純な文字列比較では「改行コード」や「装飾の差異」に足元を掬われる。シニアエンジニアであれば、`Range.Text`の比較に加え、`Range.Information`を使用して、書式変更まで追跡すべきだ。
もし大規模な監査を行うのであれば、VBA単体で完結させず、Windows API経由でクリップボードを介したバイナリ比較、あるいはFSOを利用した一時的なJSON/CSVエクスポートを挟むのが現実的なアーキテクチャだ。
差分抽出の核となるプロシージャ
‘ 変更ログを外部ファイルへストリーミングする
Private Sub ExportAuditLog(ByVal rowIdx As Long, ByVal colIdx As Long, ByVal oldVal As String, ByVal newVal As String)
Dim fso As Object
Dim ts As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ 追記モードでログを維持。レガシー環境でのファイルロックを避けるため、
‘ 排他制御(APIのLockFileEx等)が必要な場合は、適宜ラップすること
Set ts = fso.OpenTextFile(“C:\Audit\change_log.csv”, 8, True)
ts.WriteLine Format(Now, “yyyy-mm-dd hh:nn:ss”) & “,” & rowIdx & “,” & colIdx & “,” & oldVal & “,” & newVal
ts.Close
Set ts = Nothing
Set fso = Nothing
End Sub
—
3. レガシー環境を生き抜くための「Win32 API」活用
Word VBAが動作する環境は、しばしばパッチ適用が滞った旧式のWindows Serverや端末だ。`Application.Wait`のような素朴な待機処理ではなく、`kernel32.dll`の `Sleep` を使用して、CPU負荷をコントロールしつつ、バックグラウンドでのExcel連携やAPI通信時の競合を回避する。
If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Private Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
End If
‘ 巨大な表の解析時は、OSにリソースを戻すために適宜Sleepを挟む
‘ これによりシステム全体のスループットを維持する
—
4. チーフアーキテクトからの提言:完全自動化への罠
この種のツールを実装する上で、最大の敵は「ユーザーによる誤操作」ではない。「変更履歴の肥大化」だ。
表の変更をすべてログに取れば、数ヶ月でログファイルは数GBに達する。
- 差分のみを保持せよ: 変更がない場合は `Nothing` を返せ。
- 構造変更を検知せよ: 行の挿入・削除は、セルのインデックスをずらす。`Table.Rows.Add` を検知した瞬間に、監査ロジックを再帰的に呼び出し、インデックスの追跡をリセットするアーキテクチャを組むこと。
- トランザクションを意識せよ: `ActiveDocument.Undo` と組み合わせて、監査に引っかかった変更を自動的に差し戻す「ガードレール機能」を実装できれば、それはもはや単なるスクリプトではなく、立派な監査プラットフォームだ。
Wordの表は、Excelのグリッドとは異なり「文章の一部」であるという特性を忘れてはならない。その複雑性こそが、我々エンジニアが挑むべき最後のフロンティアなのだ。
コードを書く前に、データが流れる経路と、それがメモリのどの位置に展開されるかを想像せよ。それが、伝説的な自動化エンジニアへの唯一の道である。
