【実務・中級編】【上級プロフェッショナル向け】FSO一括削除・更新操作の直前に全変更対象の事前シミュレーション(Dry-Run)を行う安全機構 – VBScript (Visual Basic Scripting Edition)解析バイブル

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

破壊的処理を制御せよ:VBScriptにおける「Dry-Run」設計思想と堅牢な実装術

業務自動化の現場において、エンジニアが最も恐れる瞬間はいつか?
それは、数千、数万のファイルを対象とした一括削除や移動のスクリプトを実行し、「条件指定を誤ったまま、取り返しのつかないデータを消し去った瞬間」だ。

もし君が、`FileSystemObject` (FSO) の `DeleteFile` メソッドを、条件分岐の直後に無防備に叩き込んでいるとしたら、君の設計はプロフェッショナルとは呼べない。それは単なる「運任せのスクリプト」だ。

本記事では、実務における致命的な事故を防ぐための防波堤、すなわち「Dry-Run(シミュレーション)機構」を備えた、堅牢なファイル操作エンジンの設計手法を伝授する。

1. なぜ「いきなり実行」は悪なのか:設計の欠陥を理解する

多くの初級者が陥るミスは、「判断(Decision)」と「実行(Execution)」を同一のロジック内で混濁させている点にある。

' --- 典型的な「危ない」コードの構造 ---
For Each file In folder.Files
    If file.DateLastModified < targetDate Then
        ' ここでいきなり削除!条件が間違っていたら即終了だ。
        fso.DeleteFile file.Path
    End If
Next

この書き方の何が問題か?
1. 検証不能: 実行前に「どのファイルが対象になるか」を人間が確認する手段がない。
2. ロールバック不能: 一度削除が始まれば、途中でエラーが起きても、どこまで消したのか、何が残っているのかの把握が困難になる。
3. 副作用の制御不能: ファイル操作は、OSのファイルシステムという「共有状態」に対する破壊的操作だ。

プロフェッショナルは、「まず計画(Plan)を立て、それをレビュー(Review)し、承認(Approve)を得てから、実行(Execute)に移る」という、トランザクションに近いプロセスをコードに組み込む。

2. アーキテクチャ設計:責務の分離

堅牢なDry-Run機構を実現するには、以下の3つのフェーズに処理を分離する必要がある。

1. Scan Phase (走査フェーズ):
対象となるファイル群を特定し、操作対象のリスト(意図のリスト)を作成する。
2. Simulation Phase (シミュレーションフェーズ):
作成されたリストに基づき、ログに「何が行われるか」を書き出す。この時点では、ファイルシステムには一切の変更を加えない。
3. Execution Phase (実行フェーズ):
Dry-Runの結果を人間が確認し、問題なければ実際のFSO操作を走査リストに従って実行する。

3. プロダクション・クオリティの実装例

以下に、`Scripting.Dictionary` を用いて「操作の意図」をメモリ上に保持し、Dry-Runモードと実効モードを切り替えられるクラス構造の設計を示す。VBScriptにおけるオブジェクト指向の作法に従い、保守性を極限まで高めている。

' ===========================================================================
' FileOperationEngine.vbs
' 目的: 安全なファイル一括操作(Dry-Run機能付き)
' ===========================================================================
Option Explicit

' --- メインロジック ---
Dim engine, targetDir, logFile, isDryRun

' 設定エリア
targetDir = "C:\Temp\TestTarget" ' 操作対象ディレクトリ
logFile   = "C:\Temp\operation_log.txt"
isDryRun  = True ' ★重要: 最初は必ずTrueにしておくこと

Set engine = New FileOperationEngine
engine.Initialize targetDir, logFile

' 1. スキャンと計画策定
engine.ScanAndPlan ".*\.tmp$" ' 正規表現で拡張子 .tmp を対象とする

' 2. シミュレーション結果の出力
engine.PrintPlan

' 3. 実行の判断
If Not isDryRun Then
    Dim response
    response = MsgBox("実際の削除処理を実行しますか?", vbQuestion + vbYesNo, "最終確認")
    If response = vbYes Then
        engine.Execute
    Else
        WScript.Echo "実行がキャンセルされました。"
    End If
Else
    WScript.Echo "【Dry-Runモード】実際の操作は行われません。ログを確認してください。"
End If

' ===========================================================================
' Class: FileOperationEngine
' ===========================================================================
Class FileOperationEngine
    Private fso, targetFolder, logStream, planDict
    Private targetPath, logPath

    ' 初期化
    Public Sub Initialize(path, logPath)
        Set fso = CreateObject("Scripting.FileSystemObject")
        targetPath = path
        logPath = logPath
        Set planDict = CreateObject("Scripting.Dictionary")
        
        If Not fso.FolderExists(targetPath) Then
            Err.Raise 1, "FileOperationEngine", "Target folder does not exist: " & targetPath
        End If
    End Sub

    ' スキャンフェーズ: 操作対象を特定し、Dictionaryに格納する
    ' filterRegex: 正規表現パターン
    Public Sub ScanAndPlan(filterRegex)
        Dim folder, file, regEx
        Set folder = fso.GetFolder(targetPath)
        Set regEx = New RegExp
        regEx.Pattern = filterRegex
        regEx.IgnoreCase = True

        For Each file In folder.Files
            ' 条件判定(例:拡張子が一致するか)
            If regEx.Test(file.Name) Then
                ' Key: ファイルパス, Value: 操作種別 (今回はDeleteを想定)
                If Not planDict.Exists(file.Path) Then
                    planDict.Add file.Path, "DELETE"
                End If
            End If
        Next
    End Sub

    ' 計画の表示(ログ出力)
    Public Sub PrintPlan()
        Dim key, logMsg
        Set logStream = fso.CreateTextFile(logPath, True)
        
        logMsg = "--- Operation Plan Start ---" & vbCrLf & _
                 "Timestamp: " & Now & vbCrLf & _
                 "Target Dir: " & targetPath & vbCrLf & _
                 "----------------------------" & vbCrLf
        logStream.WriteLine logMsg

        If planDict.Count = 0 Then
            logStream.WriteLine "No files matched the criteria."
        Else
            For Each key In planDict.Keys
                logMsg = "[" & planDict(key) & "] " & key
                logStream.WriteLine logMsg
            Next
        End If

        logStream.WriteLine "--- Operation Plan End ---" & vbCrLf
        logStream.Close
        WScript.Echo "プランをログに書き出しました: " & logPath
    End Sub

    ' 実行フェーズ: 計画に基づき、実際にFSOを叩く
    Public Sub Execute()
        Dim key, successCount, errorCount
        successCount = 0
        errorCount = 0

        ' 実行ログ用
        Dim execLog
        Set execLog = fso.CreateTextFile(logPath & ".exec", True)

        For Each key In planDict.Keys
            On Error Resume Next ' 個別のエラーで全体を止めないための制御
            
            If planDict(key) = "DELETE" Then
                fso.DeleteFile key, True
                
                If Err.Number = 0 Then
                    execLog.WriteLine "[SUCCESS] Deleted: " & key
                    successCount = successCount + 1
                Else
                    execLog.WriteLine "[ERROR] Failed to delete: " & key & " (Error: " & Err.Description & ")"
                    errorCount = errorCount + 1
                    Err.Clear
                End If
            End If
            On Error GoTo 0
        Next

        execLog.WriteLine "----------------------------"
        execLog.WriteLine "Summary: Success=" & successCount & ", Error=" & errorCount
        execLog.Close
        
        WScript.Echo "実行完了。結果を確認してください: " & logPath & ".exec"
    End Sub

    ' デストラクタ代わり
    Private Sub Class_Terminate()
        Set fso = Nothing
        Set planDict = Nothing
    End Sub
End Class

4. プロフェッショナルとしての注意点:落とし穴を回避せよ

このコードを実務に投入する際、以下の3点には細心の注意を払え。

① エラーハンドリングの「質」

上記のコードでは `On Error Resume Next` を `Execute` メソッド内で局所的に使用している。これは、「1つのファイルが使用中(ロック中)であっても、他のファイルの削除処理を継続させるため」だ。しかし、これをクラス全体やグローバルに適用してはいけない。エラーを無視することは、設計の放棄と同じだ。

② ファイルロックと権限の壁

FSOは強力だが、OSレベルのファイルロック(Excelが開いている、システムプロセスが掴んでいる等)には無力だ。Dry-Runでは「ファイルが存在するか」は確認できても、「削除可能か(権限があるか)」までは完全には保証できない。実効フェーズでのエラーログ出力は、事後の調査において極めて重要な証跡となる。

③ データベースとの整合性

もし、このファイル操作に付随して「DBのレコード更新」を行う場合は、「ファイル操作の結果」と「DBの更新」をアトミック(不可分)に扱うことが困難であることを忘れるな。
最善策は、以下の順序だ。
1. Dry-Runで対象を確定。
2. DBに「処理予定フラグ」を立てる。
3. ファイル操作を実行。
4. 成功したらDBのフラグを「完了」にする。失敗したら「エラー」にする。

結論

「動いたからOK」という思考は、自動化エンジニアとしては三流だ。
「間違った時に、いかに被害を最小化し、いかに迅速に状況を把握できるか」。
この問いに対する答えが、今回解説したDry-Run機構である。

優れたコードは、機能を提供するだけでなく、「安心」を提供する。
君の書くスクリプトが、現場の信頼を勝ち取るものになることを期待している。

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