破壊的処理を制御せよ: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機構である。
優れたコードは、機能を提供するだけでなく、「安心」を提供する。
君の書くスクリプトが、現場の信頼を勝ち取るものになることを期待している。
