【実務・中級編】【上級プロフェッショナル向け】FSOファイル操作の「ロールバック(取り消し)機能」を備えた擬似トランザクションマネージャー – VBScript (Visual Basic Scripting Edition)解析バイブル

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

「動けばいい」という慢心がシステムを壊す。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` の設置場所を、十分な空き容量があるドライブに指定すること。

結論

「ファイルが消えた」「ファイルが半分移動した」という言い訳は、プロの現場では通用しない。
例外が発生することを前提とし、「失敗した時は、なかったことにする」。この一貫した設計思想こそが、業務自動化ツールの信頼性を決定づけるのである。

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