「動けばいい」という慢心がシステムを壊す。VBScriptにおけるFSO操作の「擬似トランザクション」設計論
業務自動化の現場において、多くのエンジニアが陥る致命的な罠がある。それは、「一連のファイル操作が、途中で失敗した時にどうなるか」を設計に入れていないことだ。
例えば、10個のファイルを新しいディレクトリに移動し、その後、古いディレクトリを削除するスクリプトを書いたとしよう。7個目のファイル移動で「ファイル使用中」のエラーが発生したらどうなる?
ファイルは半分移動し、半分元の場所に残り、ディレクトリは削除されず、システムは「中途半端な状態(Inconsistent State)」に陥る。これを手動で復旧させるコストを、君は計算に入れているか?
プロフェッショナルなエンジニアなら、ファイル操作にも「アトミック性(原子性)」を求めるべきだ。すべて成功するか、さもなくば、実行前の状態に完全に巻き戻す(Rollback)か。
本稿では、VBScriptの`FileSystemObject (FSO)`を用い、エラー発生時に操作履歴を逆順に辿って自動復元する「擬似トランザクションマネージャー」の実装手法を伝授する。
1. 設計思想:Undo Log(取り消しログ)パターン
ファイルシステムには、データベースのようなネイティブのトランザクション機能は存在しない。したがって、我々は自前で「Undo Log(取り消しログ)」を管理する仕組みを構築する必要がある。
設計の核心は、「操作の逆操作(Inverse Operation)をスタックに積み上げる」ことにある。
| 実行した操作 | 発生した状態の変化 | 準備すべき「逆操作」 |
|---|---|---|
| CreateFile | ファイルが生成される | そのファイルを Delete する |
| MoveFile | AからBへ移動する | BからAへ Move し直す |
| DeleteFile | ファイルが消える | ※重要:事前に一時フォルダへ退避させておく |
特に「削除」については注意が必要だ。真に削除してしまうと、ロールバックは不可能になる。プロフェッショナルな設計では、`Delete` 命令が飛んできた際、物理削除ではなく「一時領域への移動(Move to Temp)」として扱うのが鉄則である。
2. 実装:FileTransactionManager クラス
以下に、堅牢な設計に基づいたプロダクションレベルのコードを示す。このクラスは、操作をスタックに記録し、エラー発生時に `Rollback` メソッドを呼び出すことで、状態を完全に復元する。
'**
' Class: FileTransactionManager
' Description: FSO操作のロールバック機能を備えた擬似トランザクションマネージャー
'**
Class FileTransactionManager
Private fso
Private undoStack ' 操作の逆操作を格納するCollection
Private tempDir ' 削除ファイルの退避用一時ディレクトリ
Private isCommitted ' コミット済みフラグ
Private Sub Class_Initialize()
Set fso = CreateObject("Scripting.FileSystemObject")
Set undoStack = CreateObject("Scripting.Collection")
' 一時ディレクトリの作成 (Tempフォルダ内に一意のフォルダを作成)
tempDir = fso.GetSpecialFolder(2) & "\VBS_TX_TEMP_" & Timer
fso.CreateFolder(tempDir)
isCommitted = False
End Sub
Private Sub Class_Terminate()
' クリーンアップ: コミットされていない場合や一時ファイルが残っている場合の処理
' 本来はここでTempDir内の残骸を整理するロジックを検討する
Set fso = Nothing
Set undoStack = Nothing
End Sub
'--------------------------------------------------------------------------
' Method: CreateFile
'--------------------------------------------------------------------------
Public Sub CreateFile(filePath, content)
On Error Resume Next
Dim ts
Set ts = fso.CreateTextFile(filePath, True)
If Err.Number <> 0 Then
Err.Raise Err.Number, "CreateFile", "Failed to create file: " & filePath
End If
ts.WriteLine content
ts.Close
' 逆操作をスタックに登録: 「作成したファイルを削除する」
undoStack.Add Array("DELETE", filePath), Nothing
On Error GoTo 0
End Sub
'--------------------------------------------------------------------------
' Method: MoveFile
'--------------------------------------------------------------------------
Public Sub MoveFile(sourcePath, destPath)
On Error Resume Next
fso.MoveFile sourcePath, destPath
If Err.Number <> 0 Then
Err.Raise Err.Number, "MoveFile", "Failed to move: " & sourcePath
End If
' 逆操作をスタックに登録: 「移動したファイルを元の場所に戻す」
undoStack.Add Array("MOVE", destPath, sourcePath), Nothing
On Error GoTo 0
End Sub
'--------------------------------------------------------------------------
' Method: DeleteFile (Pseudo-Delete)
'--------------------------------------------------------------------------
Public Sub DeleteFile(filePath)
On Error Resume Next
Dim fileName, tempPath
fileName = fso.GetFileName(filePath)
tempPath = tempDir & "\" & fileName
' 物理削除せず、一時フォルダへ移動させる(これがプロのやり方だ)
fso.MoveFile filePath, tempPath
If Err.Number <> 0 Then
Err.Raise Err.Number, "DeleteFile", "Failed to move to temp: " & filePath
End If
' 逆操作をスタックに登録: 「一時フォルダから元の場所へ戻す」
undoStack.Add Array("RESTORE", tempPath, filePath), Nothing
On Error GoTo 0
End Sub
'--------------------------------------------------------------------------
' Method: Commit
'--------------------------------------------------------------------------
Public Sub Commit()
' コミット成功:Undoログを破棄し、一時ファイルを完全に消去する
isCommitted = True
' 一時フォルダ内のファイルを物理削除
On Error Resume Next
fso.DeleteFile tempDir & "\*", 1
fso.DeleteFolder tempDir
On Error GoTo 0
Set undoStack = CreateObject("Scripting.Collection")
End Sub
'--------------------------------------------------------------------------
' Method: Rollback
'--------------------------------------------------------------------------
Public Sub Rollback()
If isCommitted Then Exit Sub
Dim i, action, params
' スタックの最後(最新の操作)から順に逆操作を実行する
For i = undoStack.Count To 1 Step -1
action = undoStack(i)(0)
Select Case action
Case "DELETE"
' 作成したファイルを消す
If fso.FileExists(undoStack(i)(1)) Then fso.DeleteFile undoStack(i)(1)
Case "MOVE"
' 移動したファイルを戻す
fso.MoveFile undoStack(i)(1), undoStack(i)(2)
Case "RESTORE"
' 退避させていたファイルを戻す
fso.MoveFile undoStack(i)(1), undoStack(i)(2)
End Select
Next
' 復旧後、一時ディレクトリを掃除
On Error Resume Next
fso.DeleteFolder tempDir, True
On Error GoTo 0
End Sub
End Class
3. 実務での運用例:エラーハンドリングとの統合
このクラスを単体で使うのは素人だ。真の力は、`Try-Catch` 構造(VBScriptでは `On Error Resume Next` と `Err` オブジェクトの組み合わせ)と組み合わせた時に発揮される。
Dim tx, fso
Set fso = CreateObject("Scripting.FileSystemObject")
Set tx = New FileTransactionManager
On Error Resume Next ' エラーハンドリング開始
' --- トランザクション開始 ---
' 1. 新規レポートファイルの作成
tx.CreateFile "C:\Work\Report_2023.txt", "Data Content"
' 2. 既存のバックアップを移動
tx.MoveFile "C:\Work\Old_Backup.bak", "C:\Work\Archived_Backup.bak"
' 3. 意図的にエラーを発生させるテスト (存在しないファイルを移動しようとする)
tx.MoveFile "C:\Work\NonExistent.txt", "C:\Work\Error_File.txt"
' エラーチェック
If Err.Number <> 0 Then
WScript.Echo "Critical Error Detected: " & Err.Description
WScript.Echo "Initiating Rollback..."
tx.Rollback ' ここで全操作が自動的に巻き戻される
WScript.Echo "Rollback Complete. System state restored."
Err.Clear
Else
tx.Commit ' すべて成功した場合のみ確定
WScript.Echo "Transaction Committed Successfully."
End If
On Error GoTo 0 ' エラーハンドリング終了
4. プロフェッショナルが留意すべき「落とし穴」
この設計を実務に投入する際、以下の3点には細心の注意を払え。
① ファイルロックの競合
`Rollback` を実行しようとした際、操作対象のファイルが他のプロセス(Excelやウイルス対策ソフト)によってロックされていると、ロールバック自体が失敗する。これは「ロールバックの失敗」という、最悪の事態を招く。
対策: ロールバック実行前に、対象ファイルがロックされていないか確認するロジック、あるいはリトライメカニズムを検討せよ。
② データベースとの整合性
もしファイル操作と同時にデータベース(AccessやSQL Server等)の更新を行っている場合、ファイルだけをロールバックしてもデータベースは更新されたままになる。
対策: データベースのトランザクションとこのマネージャーを同期させる必要がある。ファイル操作の成功をトリガーに、DBの `Commit` を呼ぶ設計にせよ。
③ ストレージ容量の枯渇
`DeleteFile` を「一時フォルダへの移動」で代用しているため、大量のファイルを削除するトランザクションでは、一時フォルダがストレージを圧迫する。
対策: `tempDir` の設置場所を、十分な空き容量があるドライブに指定すること。
結論
「ファイルが消えた」「ファイルが半分移動した」という言い訳は、プロの現場では通用しない。
例外が発生することを前提とし、「失敗した時は、なかったことにする」。この一貫した設計思想こそが、業務自動化ツールの信頼性を決定づけるのである。
