破壊的変更を「制御可能なイベント」に変える:FSOにおけるSnapshot & Undoエンジンの設計論
自動化エンジニアとして現場に立つとき、我々が最も恐れるべきは何のバグでもない。「取り返しのつかない破壊的操作」だ。
大量のファイルを一括でリネームする、あるいはディレクトリ構造を再編する。こうしたスクリプトを「動くから」という理由だけで、事前の状態保存(Snapshot)なしにデプロイする者は、エンジニアではなく単なる「スクリプタ」に過ぎない。
プロフェッショナルが作るツールには、必ず「退路(Undo)」が用意されている。本稿では、VBScriptのFileSystemObject (FSO) を駆使し、操作前のメタデータを完全に記録し、万が一の誤操作時に一瞬で元の状態へ復元する「Snapshot & Undoエンジン」の設計と実装について、その極限の知見を伝授する。
1. なぜ「単なるループ処理」では足りないのか
一般的な自動化スクリプトは、以下のような構造になりがちだ。
1. 対象フォルダを走査する
2. 条件に合致するファイルをリネーム/移動する
この設計の致命的な欠陥は、「操作の原子性(Atomicity)」が欠如している点にある。
途中で権限エラーが発生した、あるいは正規表現のロジックが僅かに間違っていた場合、ファイル群は「半分リネームされた中途半端な状態」で放置される。この状態からの復旧は、もはや手作業による地獄の作業となる。
我々が目指すべきは、「状態の記録(Snapshot)」と「実行(Execution)」を完全に分離した設計である。
設計の三原則
1. Metadata First: 操作を開始する前に、対象ファイルの「現時点の真実(Path, Size, Timestamp, Name)」を外部ファイルに書き出す。
2. Idempotency (冪等性) の意識: Undo操作自体が失敗しても、二度実行しても状態を壊さない設計にする。
3. Single Source of Truth: ログファイルこそが、システムの状態を示す唯一の正解とする。
2. アーキテクチャ:Snapshot & Undo Engine
設計図はシンプルだが、堅牢である必要がある。
1. Phase 1: Snapshot Generation
対象となる全ファイルのメタデータを抽出し、CSV形式(または構造化されたテキスト)で保存する。この際、「変更後のパス」もあらかじめ計算して含めておくことが肝要だ。
2. Phase 2: Execution
Snapshotに基づいて実際のFSO操作を行う。
3. Phase 3: Rollback (Undo)
Snapshotファイルを読み込み、`NewPath` を `OldPath` に、`OldPath` を `NewPath` に入れ替える逆転操作を実行する。
3. プロダクション・コード:FileActionEngine
以下に、クラスベースでカプセル化された、実戦投入可能なコード例を示す。VBScriptにおいて、変数のスコープとエラーハンドリングを制御するため、クラス設計は必須である。
' =============================================================================
' FileActionEngine Class
' 目的: ファイル操作のSnapshot作成、実行、およびUndo機能の提供
' 設計思想: 破壊的操作の前に必ずメタデータを記録し、復元可能性を担保する
' =============================================================================
Class FileActionEngine
Private FSO
Private SnapshotFile
Private Delimiter
' コンストラクタ
Private Sub Class_Initialize()
Set FSO = CreateObject("Scripting.FileSystemObject")
Delimiter = "|" ' CSVより安全な区切り文字を選択
SnapshotFile = ""
End Sub
' --- メソッド: Snapshotの作成 ---
' 対象フォルダ内のファイルを走査し、変更計画をSnapshotとして保存する
Public Function CreateSnapshot(targetFolder, renameRuleFunc)
' 注: VBScriptでは関数を引数に渡せないため、
' 本来はロジックをクラス内メソッドとして定義するか、
' 外部から定義されたルールに基づいて処理を行う必要がある。
' ここでは簡略化のため、直接的なパス操作ロジックを想定。
On Error Resume Next
Dim ts, folder, file, oldPath, newPath, metaLine
Set folder = FSO.GetFolder(targetFolder)
' Snapshotファイルの生成 (実行ディレクトリにタイムスタンプ付きで作成)
SnapshotFile = "snapshot_" & Replace(Replace(Now, "/", ""), ":", "") & ".log"
Set ts = FSO.CreateTextFile(SnapshotFile, True)
For Each file In folder.Files
oldPath = file.Path
' 【重要】ここで「変更後のパス」を計算する
' 実務ではここを高度な正規表現ロジックに置き換える
newPath = Replace(oldPath, "old_", "new_")
' メタデータの記録: 旧パス|新パス|サイズ|更新日時
' これにより、Undo時に「何がどう変わるべきか」が完全にわかる
metaLine = oldPath & Delimiter & newPath & Delimiter & _
file.Size & Delimiter & file.DateLastModified
ts.WriteLine metaLine
Next
ts.Close
If Err.Number <> 0 Then
MsgBox "Snapshot作成失敗: " & Err.Description, vbCritical
CreateSnapshot = False
Else
MsgBox "Snapshot作成完了: " & SnapshotFile & vbCrLf & "操作を開始できます。", vbInformation
CreateSnapshot = True
End If
On Error GoTo 0
End Function
' --- メソッド: 変更の実行 ---
' Snapshotに基づいて実際にファイル名を変更する
Public Sub Execute()
If SnapshotFile = "" Then Exit Sub
Dim ts, line, parts, oldP, newP
Set ts = FSO.OpenTextFile(SnapshotFile, 1) ' Read mode
Do Until ts.AtEndOfStream
line = ts.ReadLine
parts = Split(line, Delimiter)
If UBound(parts) >= 1 Then
oldP = parts(0)
newP = parts(1)
' 実行前に存在確認を行う堅牢な設計
If FSO.FileExists(oldP) Then
' 変更先が既に存在する場合はエラーを回避
If Not FSO.FileExists(newP) Then
FSO.MoveFile oldP, newP
Else
' ログにスキップを記録(実務ではログファイルへの書き出しが望ましい)
End If
End If
End If
Loop
ts.Close
MsgBox "一括変更処理が完了しました。", vbInformation
End Sub
' --- メソッド: Undo (一括復元) ---
' Snapshotを逆引きして、元の状態に戻す
Public Sub Undo()
If SnapshotFile = "" Or Not FSO.FileExists(SnapshotFile) Then
MsgBox "有効なSnapshotファイルが見つかりません。", vbExclamation
Exit Sub
End If
Dim ts, line, parts, oldP, newP
Set ts = FSO.OpenTextFile(SnapshotFile, 1)
Do Until ts.AtEndOfStream
line = ts.ReadLine
parts = Split(line, Delimiter)
If UBound(parts) >= 1 Then
oldP = parts(0)
newP = parts(1)
' 【逆転ロジック】新パス(newP)から旧パス(oldP)へ戻す
If FSO.FileExists(newP) Then
If Not FSO.FileExists(oldP) Then
FSO.MoveFile newP, oldP
End If
End If
End If
Loop
ts.Close
MsgBox "Undo操作が完了しました。元の状態を確認してください。", vbInformation
End Sub
End Class
' =============================================================================
' メインルーチン (Driver)
' =============================================================================
Dim engine
Set engine = New FileActionEngine
' 1. Snapshotの作成
If engine.CreateSnapshot("C:\TestFolder") Then
' 2. ユーザーの確認を待つ(ここがプロの作法)
Dim ans
ans = MsgBox("変更を実行しますか?", vbYesNo + vbQuestion, "実行確認")
If ans = vbYes Then
engine.Execute
' 3. 失敗した場合のUndoテスト(実務ではエラーハンドリング内で呼ぶ)
Dim undoAns
undoAns = MsgBox("Undo(元に戻す)を実行しますか?", vbYesNo + vbQuestion, "復元確認")
If undoAns = vbYes Then
engine.Undo
End If
End If
End If
4. 現場で陥る「落とし穴」と回避策
このエンジンを運用する上で、以下の3点には細心の注意を払え。
① ファイルロックの競合
FSOの `MoveFile` は、対象ファイルがExcelや他のプロセスによって開かれている場合、即座に実行時エラーを吐く。
対策: `Execute` メソッド内で `On Error Resume Next` を適切に使い、失敗した行を「エラーログ」としてSnapshotとは別に記録する設計にせよ。全てのファイルが成功したか、一部失敗したのかを明確に区別できなければ、Undoの整合性が崩れる。
② パス長制限 (MAX_PATH)
Windowsの伝統的な制限(260文字)により、深い階層でのリネームは失敗する。
対策: 非常に深いディレクトリを扱う場合は、パスの先頭に `\\?\` プレフィックスを検討するか、対象範囲を限定する運用ルールを徹底せよ。
③ Snapshotの整合性破壊
Snapshotファイル自体が改ざんされたり、削除されたりした場合、Undoは不可能になる。
対策: Snapshotファイルは「実行用の一時データ」として扱うが、重要な業務プロセスにおいては、Snapshotファイルを読み取り専用属性 (`File.Attributes = 1`) に設定し、保護をかけるのがプロの振る舞いだ。
5. 結論:エンジニアとしての矜持
「動けばいい」という思考は、自動化の世界では「いつか必ず破綻する」と同義である。
優れた自動化エンジニアとは、コードの行数が多い者でも、複雑なアルゴリズムを駆使する者でもない。「失敗したときに、いかに安全に、いかに速やかに、いかに確実に元に戻せるか」を、設計の第一段階から考慮できる者のことだ。
このSnapshot & Undoパターンをマスターし、あなたのツールに「責任」という名の堅牢性を宿らせてほしい。
