【極限の知見】VBScriptにおけるファイル操作の原子性を担保せよ:擬似トランザクションマネージャーの実装論
エンタープライズの最前線において、スクリプトによるファイル操作は常に「不完全性」というリスクを孕んでいる。
FileSystemObject (FSO) は、極めて直感的で強力なインターフェースを提供する。しかし、その設計思想には「原子性(Atomicity)」が欠落している。複数のファイルを作成、移動、削除する一連のプロセスにおいて、途中でネットワーク断線や権限エラー、ディスクフルが発生したとしよう。FSOはその時点で処理を停止するが、それまでに実行された操作を「無かったこと」にする機能は持ち合わせていない。
結果として、システムには「中途半端に作成されたファイル」や「消失したデータ」が残り、手動でのリカバリ、あるいはより大規模なシステム復旧作業という、エンジニアにとって最も忌むべきコストを強いることになる。
本稿では、この脆弱性を克服するため、VBScriptにおいてコマンドパターンを用いた「擬似トランザクションマネージャー」を実装する極限の設計手法を解説する。
1. アーキテクチャの設計思想:逆操作のスタック管理
トランザクションを実現するためには、実行したすべての操作に対して、それを完全に打ち消す「逆操作(Undo Action)」を定義し、それをLIFO(Last-In, First-Out)のスタック構造で保持する必要がある。
ここで重要なのは、単に「削除」を記録するのではないということだ。
逆操作の定義マトリクス
| 実行操作 (Action) | 逆操作 (Inverse Action) | 備考 |
|---|---|---|
| CreateFile | DeleteFile | 作成したファイルを削除する |
| MoveFile | MoveFile (Reverse) | 元のパスへ移動し直す |
| DeleteFile | RestoreFile | 重要: 物理削除せず、一時領域へ退避させる |
| RenameFile | RenameFile (Reverse) | 元の名前に戻す |
特に「Delete」の扱いは、シニアエンジニアとしての腕の見せ所だ。FSOの `DeleteFile` を直接呼び出してしまった後では、トランザクションは破綻する。我々のマネージャーは、削除要求を「物理的な消去」ではなく「隠蔽された一時ディレクトリへの移動」として解釈し、ロールバック時にのみ真の消去を行う。
2. 実装:FileSystemTransactionManager クラス
以下に、実戦投入に耐えうる設計思想に基づいたクラス実装を示す。このコードは、VBAおよびVBScript環境で動作するように設計されている。
' ==============================================================================
' Class: FileSystemTransactionManager
' Description: FSO操作の原子性を確保するための擬似トランザクションマネージャー
' Author: Legendary Chief Architect
' ==============================================================================
Class FileSystemTransactionManager
Private FSO
Private ActionStack ' 操作履歴を格納するコレクション
Private TempStagingDir ' 削除ファイルの退避用ディレクトリ
Private IsInTransaction ' トランザクション中フラグ
' --------------------------------------------------------------------------
' Constructor: 初期化
' --------------------------------------------------------------------------
Private Sub Class_Initialize()
Set FSO = CreateObject("Scripting.FileSystemObject")
Set ActionStack = CreateObject("Scripting.Dictionary") ' 簡易スタックとして利用
' 実際には、操作順序を厳密に管理するため、Index管理が必要
Set ActionStack = CreateObject("System.Collections.ArrayList")
' 退避用ディレクトリの作成(%TEMP%配下)
TempStagingDir = FSO.GetSpecialFolder(2) & "\VBS_TRANS_" & Timer()
IsInTransaction = False
End Sub
' --------------------------------------------------------------------------
' BeginTransaction: トランザクションの開始
' --------------------------------------------------------------------------
Public Sub BeginTransaction()
If IsInTransaction Then Err.Raise 1001, "TM", "Transaction already in progress."
If Not FSO.FolderExists(TempStagingDir) Then
FSO.CreateFolder(TempStagingDir)
End If
IsInTransaction = True
' スタックのクリア
Do While ActionStack.Count > 0
ActionStack.RemoveAt 0
Loop
End Sub
' --------------------------------------------------------------------------
' SafeDelete: 削除操作(物理削除せず退避)
' --------------------------------------------------------------------------
Public Sub SafeDelete(ByVal targetPath)
If Not IsInTransaction Then Err.Raise 1002, "TM", "No active transaction."
Dim fileName, stagingPath
fileName = FSO.GetFileName(targetPath)
stagingPath = TempStagingDir & "\" & Timer() & "_" & fileName
' 実際の削除ではなく、退避を実行
FSO.MoveFile targetPath, stagingPath
' 逆操作(復元)をスタックに積む
' パターン: [Type, OriginalPath, StagingPath]
ActionStack.Add Array("DELETE", targetPath, stagingPath)
End Sub
' --------------------------------------------------------------------------
' SafeMove: 移動操作
' --------------------------------------------------------------------------
Public Sub SafeMove(ByVal source, ByVal dest)
If Not IsInTransaction Then Err.Raise 1002, "TM", "No active transaction."
FSO.MoveFile source, dest
' 逆操作(元の場所へ戻す)をスタックに積む
ActionStack.Add Array("MOVE", source, dest)
End Sub
' --------------------------------------------------------------------------
' Rollback: 全操作の取り消し
' --------------------------------------------------------------------------
Public Sub Rollback()
On Error Resume Next ' ロールバック中のエラーは致命的
' スタックを末尾(最新の操作)から順に処理
Dim i, action
For i = ActionStack.Count - 1 To 0 Step -1
action = ActionStack(i)
Select Case action(0)
Case "DELETE"
' 退避していたファイルを元の場所に戻す
FSO.MoveFile action(2), action(1)
Case "MOVE"
' 移動したファイルを元の場所に戻す
FSO.MoveFile action(2), action(1)
Case "CREATE"
' 作成したファイルを削除
FSO.DeleteFile action(1)
End Select
Next
' 後始末
Cleanup
End Sub
' --------------------------------------------------------------------------
' Commit: トランザクションの確定
' --------------------------------------------------------------------------
Public Sub Commit()
If Not IsInTransaction Then Exit Sub
' 確定時は、退避していた「削除済みファイル」を物理的に消去する
Cleanup
IsInTransaction = False
End Sub
' --------------------------------------------------------------------------
' Private: リソースの解放とクリーンアップ
' --------------------------------------------------------------------------
Private Sub Cleanup()
' 退避ディレクトリの物理削除
If FSO.FolderExists(TempStagingDir) Then
FSO.DeleteFolder TempStagingDir, True
End If
' オブジェクトの明示的解放(メモリリーク防止)
Set ActionStack = Nothing
Set FSO = Nothing
IsInTransaction = False
End Sub
' Destructor
Private Sub Class_Terminate()
Cleanup
End Sub
End Class
3. プロフェッショナルのための技術深掘り
3.1 メモリ管理とオブジェクトのライフサイクル
VBScriptにおける `Set obj = Nothing` は、単なる作法ではない。特に `FileSystemObject` や `Dictionary` などのCOMオブジェクトを多用する場合、明示的な解放を怠ると、スクリプト終了後もプロセスがゾンビ化したり、ファイルハンドルがロックされたままになるリスクがある。本実装では `Class_Terminate` を活用し、オブジェクトの破棄を確実に行っている。
3.2 Windows APIとの境界線
FSOは高レベルな抽象化を提供してくれるが、低レベルな制御(例:ファイルロックの強制解除や、NTFSのジャーナルへの直接アクセス)が必要な場合は、`Declare Function` を用いた `kernel32.dll` への呼び出しを検討すべきだ。しかし、本稿のようなトランザクション管理においては、FSOの抽象化を利用しつつ、「論理的な状態遷移」をコードで制御することに注力するのが、保守性の観点から最も賢明な判断となる。
3.3 パフォーマンスへの影響
このマネージャーは、すべての操作に対して「逆操作の記録」を行うため、通常のFSO操作に比べてI/Oオーバーヘッドが増大する。特に大量のファイルを扱う場合、`TempStagingDir` への移動コストを無視できない。
しかし、「データの整合性」という価値は、数秒の実行速度よりも遥かに重い。 実行速度を優先すべきバッチ処理と、整合性を優先すべきシステム操作を明確に使い分けること。これがアーキテクトの責務である。
4. 実践的な利用パターン
以下に、エラー発生時に自動的にロールバックを行う実装例を示す。
Dim tm
Set tm = New FileSystemTransactionManager
On Error Resume Next ' エラーハンドリングの開始
tm.BeginTransaction()
' --- シナリオ: ファイルの移動と一部削除のセット ---
tm.SafeMove "C:\Data\Source.txt", "C:\Data\Dest.txt"
tm.SafeDelete "C:\Data\OldLog.log"
' 意図的なエラー発生のシミュレーション
' もしここでエラーが起きれば、Dest.txtは元に戻り、OldLog.logは復元される
' Err.Raise 1
If Err.Number <> 0 Then
WScript.Echo "Error detected: " & Err.Description & ". Rolling back..."
tm.Rollback()
Else
tm.Commit()
WScript.Echo "Transaction completed successfully."
End If
Set tm = Nothing
結語
VBScriptはレガシーな技術と見なされがちだが、その制約を知り尽くし、設計によって制約を凌駕するプロセスは、現代のエンジニアリングにおいても変わらぬ真理である。
「失敗したときにどうするか」を設計に組み込める者だけが、真に信頼されるシステムを構築できる。
