VBScriptを掌握せよ:FSOとDictionaryで実現する「差分マトリックス」の極致
現場で「とりあえずフォルダを比較して」と言われたとき、多くのエンジニアは単純なループを二重に回すという「計算量の暴力」に逃げる。だが、数千、数万のファイルを扱う業務において、その実装はシステムを停止させる地雷に他ならない。
今日は、VBScriptの限界を知り尽くした者が到達する、`Scripting.FileSystemObject (FSO)`と`Scripting.Dictionary`を組み合わせた「高効率な差分抽出アルゴリズム」を伝授する。
1. なぜ「二重ループ」は罪なのか
ファイルリストを片っ端から比較するコードを書くと、計算量は $O(N \times M)$ になる。ファイル数が1,000件あれば100万回の比較だ。ネットワークドライブ越しなら、それだけで数分のロスになる。
我々が目指すべきは $O(N + M)$ の線形時間だ。
`Scripting.Dictionary`を「ハッシュマップ」として活用し、キー検索を$O(1)$で行う。 これが、実務レベルのツールを「道具」から「資産」に変える唯一の道である。
2. アーキテクチャ設計の指針
今回作成するスクリプトの要諦は以下の3点だ。
1. 正規化: パスやファイル名の揺らぎを排除し、キーを一意に定める。
2. 疎結合な収集: ファイル収集フェーズと差分判定フェーズを分離する。
3. ストリーム出力: メモリを圧迫しないよう、結果は逐次CSVとして書き出す。
3. プロダクションコード:高速差分マトリックス生成
このコードは、単に動くだけでなく、大規模なファイル群でも耐えうる設計としている。
‘ — 設定エリア —
Const FolderA = “C:\Source\Path1”
Const FolderB = “C:\Source\Path2”
Const ReportPath = “C:\Output\Comparison_Result.csv”
‘ — メイン処理 —
Call CompareFolders(FolderA, FolderB, ReportPath)
Sub CompareFolders(path1, path2, outPath)
Dim fso, dict1, dict2
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set dict1 = CreateObject(“Scripting.Dictionary”)
Set dict2 = CreateObject(“Scripting.Dictionary”)
‘ 1. ファイル情報の収集(ハッシュマップ化)
‘ 相対パスをキーにすることで、フォルダ構成を維持した比較が可能
CollectFiles fso.GetFolder(path1), path1, dict1
CollectFiles fso.GetFolder(path2), path2, dict2
‘ 2. CSV出力準備
Dim ts
Set ts = fso.CreateTextFile(outPath, True)
ts.WriteLine “FilePath,Status”
‘ 3. 差分判定ロジック
‘ dict1を基準に走査
Dim key
For Each key In dict1.Keys
If dict2.Exists(key) Then
ts.WriteLine key & “,両方に存在”
dict2.Remove(key) ‘ 処理済みを削除
Else
ts.WriteLine key & “,フォルダAのみ”
End If
Next
‘ 残ったdict2はフォルダBにしかないもの
For Each key In dict2.Keys
ts.WriteLine key & “,フォルダBのみ”
Next
ts.Close
MsgBox “差分レポートの生成が完了しました。”, 64
End Sub
‘ 再帰的にフォルダを探索するプロシージャ
Sub CollectFiles(folder, root, dict)
Dim file, subFolder
For Each file In folder.Files
‘ 相対パスを計算してキーにする
Dim relPath
relPath = Mid(file.Path, Len(root) + 2)
dict.Add relPath, file.Size
Next
For Each subFolder In folder.SubFolders
CollectFiles subFolder, root, dict
Next
End Sub
4. 現場で生き残るための「こだわり」
1. 相対パスの正規化
`file.Path`をそのままキーにすると、当然ながら「フォルダA」と「フォルダB」で別物と判定される。`Mid(file.Path, Len(root) + 2)`という手法で、ルートからの相対パスに変換しているのがミソだ。これで、構成が同じであれば名前が一致するかを論理的に判定できる。
2. Dictionaryの「削除」というテクニック
最後に残った`dict2`をそのまま出力する設計にしている。これにより、`dict1`を走査した後の「片方のみ」の判定ロジックを極めてシンプルに保っている。無駄な条件分岐はバグの温床だ。
3. パフォーマンスへの配慮
もし対象が数万ファイルを超える場合は、`Scripting.Dictionary`の`CompareMode`を`vbTextCompare`(大文字小文字を区別しない)に設定することを検討せよ。また、読み取り専用のファイルや権限エラーが発生するフォルダへの対策として、`On Error Resume Next`を局所的に使用し、ログを吐き出す例外処理を組み込むのが「プロ」の流儀だ。
最後に:エンジニアとしての矜持
VBScriptは古い言語だと言われる。しかし、OSのネイティブ環境でこれほどまでに高速かつ低リソースに動作する言語は他にない。
「動くコード」を作るのは新人の仕事だ。我々アーキテクトがやるべきは、「誰が保守しても壊れず、どんな負荷にも耐えうる、美しいロジック」を書き残すことである。このコードをベースに、君の現場の「非効率」を根こそぎ自動化してほしい。
健闘を祈る。
