限界を超えろ。VBScriptによる「非再帰型」超深層フォルダ走査エンジンの設計思想
業務自動化の現場において、VBScriptは今なお強力な武器である。しかし、多くの「自称エンジニア」が書くスクリプトには、プロフェッショナルとして見過ごせない致命的な設計上の欠陥が潜んでいる。
その最たるものが、フォルダ走査における「再帰関数(Recursion)」への依存だ。
「フォルダの中にフォルダがあり、その中にまたフォルダがある……。だから関数の中で自分自身を呼び出せばいい」
この思考停止こそが、大規模なファイルサーバーや数千階層に及ぶディレクトリ構造に直面した際、あなたのスクリプトを「スタックオーバーフロー」という名の死へと追いやる。本稿では、そのような初歩的な罠を回避し、実務で耐えうる堅牢な「非再帰型フォルダ走査エンジン」の構築術を伝授する。
1. なぜ、あなたの再帰関数は「現場」で死ぬのか
まず、問題の本質を理解せよ。
一般的な再帰アルゴリズムでは、関数が自分自身を呼び出すたびに、コンピュータの「コールスタック」というメモリ領域に、現在の関数実行状態(ローカル変数や戻り先アドレス)が積み上げられていく。
VBScript(およびその基盤となるエンジン)のコールスタックには、物理的な上限が存在する。もし、対象のディレクトリ構造が想定以上に深かった場合、あるいは無限ループに近い構造に陥った場合、スタック領域は即座に枯渇する。結果として、スクリプトはエラーメッセージすら吐かずに沈黙するか、実行時エラーで強制終了する。
これは「動けばいい」レベルのコードでは絶対に許されない。エンタープライズ環境における自動化ツールは、「どんなに深い構造であっても、メモリ(ヒープ領域)が許す限り完走すること」が絶対条件である。
2. 設計思想:コールスタックを捨て、ヒープを活用せよ
スタックオーバーフローを回避するための唯一の解は、「再帰(Recursion)」を「反復(Iteration)」に書き換えることだ。
具体的には、関数の呼び出しを利用して階層を管理するのではなく、「次に探索すべきフォルダのリスト」をデータ構造(キューまたはスタック)として明示的に管理する手法をとる。
- 再帰型: プログラムの実行制御(コールスタック)に階層管理を任せる。
- 非再帰型: プログラムが管理するデータ構造(ヒープ領域上の配列やコレクション)に階層管理を任せる。
ヒープ領域はコールスタックに比べて圧倒的に広大だ。この設計転換こそが、プロフェッショナルへの第一歩である。
3. 実装:非再帰型走査エンジンのプロトタイプ
ここでは、`System.Collections.ArrayList` を利用して、スタック構造(LIFO: Last-In, First-Out)を用いた非再帰型エンジンを実装する。VBScript単体では高度なスタック操作は難しいが、Windows環境であればCOM経由で .NET の `ArrayList` を呼び出すのが最もスマートで高速な解決策だ。
'**
' Engine: Non-Recursive Deep Folder Scanner
' Description: Stack-based traversal to avoid Stack Overflow.
' Author: Chief Architect
'**
Option Explicit
Dim fso, rootPath, stack, targetFolder
Set fso = CreateObject("Scripting.FileSystemObject")
' --- 設定領域 ---
rootPath = "C:\Your\Target\Path" ' 走査対象のルート
' ----------------
' 探索待ちのフォルダを格納するスタック (System.Collections.ArrayListを使用)
' これにより、コールスタックではなくヒープメモリを利用して階層を管理する
Set stack = CreateObject("System.Collections.ArrayList")
If Not fso.FolderExists(rootPath) Then
WScript.Echo "Error: Target path does not exist."
WScript.Quit
End If
' 初期値としてルートフォルダをスタックに積む
stack.Add rootPath
WScript.Echo "Starting scan..."
' メインループ:スタックが空になるまで継続
Do While stack.Count > 0
' スタックから最後の要素を取り出す (LIFO)
Dim currentPath
currentPath = stack[stack.Count - 1]
stack.RemoveAt stack.Count - 1
On Error Resume Next ' 権限エラー対策
Dim currentFolder
Set currentFolder = fso.GetFolder(currentPath)
If Err.Number <> 0 Then
' アクセス拒否等のエラー時はログ出力してスキップ
WScript.Echo "Access Denied or Error: " & currentPath
Err.Clear
Else
' 1. 現在のフォルダ内のファイルを処理
ProcessFiles currentFolder
' 2. サブフォルダをスタックに積む(これが非再帰の肝)
Dim subFolder
For Each subFolder In currentFolder.SubFolders
stack.Add subFolder.Path
Next
End If
On Error GoTo 0
Loop
WScript.Echo "Scan completed."
'**
' Sub: ProcessFiles
' Description: ファイルに対するビジネスロジックを記述する場所
'**
Sub ProcessFiles(folderObj)
Dim fileObj
For Each fileObj In folderObj.Files
' ここにファイルに対する処理(DB登録、ログ出力、移動など)を記述
' 例: WScript.Echo "File: " & fileObj.Path
Next
End Sub
4. プロフェッショナルのための「堅牢性」への補足
上記のコードは基本形だが、実務の戦場ではこれだけでは足りない。以下の3点を設計に組み込め。
① エラーハンドリングの「外科手術」
`On Error Resume Next` をスクリプト全体にばら撒くのは素人のすることだ。エラーが発生しうる「境界線(フォルダへのアクセスやファイル操作)」に対してのみ、ピンポイントで適用せよ。特に、システムフォルダ(`System Volume Information` 等)へのアクセス拒否は必ず発生する。これを無視せず、ログに記録して「継続可能」な設計にすることが重要だ。
② 外部連携(DB/CSV)のオーバーヘッド
走査中にファイル名を一つずつデータベースに `INSERT` しているようでは、処理速度は亀のように遅くなる。
- バッチ処理: 1,000件単位でメモリに溜め込み、一括で `Bulk Insert` する。
- ログの非同期化: ログ出力自体がボトルネックにならないよう、最小限の情報を書き出す。
③ メモリリークの防止
ループ内で `Set currentFolder = …` とオブジェクトを生成し続ける場合、ループの終端で適切に `Set currentFolder = Nothing` を行う習慣をつけろ。VBScriptのガベージコレクションはそれほど過信できるものではない。大規模走査では、この一歩がメモリ消費の安定性を左右する。
結論
「再帰」は美しい。しかし、「業務」は美しさよりも「生存性」を求める。
深層フォルダ走査を実装する際は、コールスタックという不安定な土台を捨て、明示的なデータ構造による制御を選択せよ。それが、どんなに複雑なディレクトリ構造を突きつけられても、確実に、そして高速に任務を遂行するための、プロフェッショナルの流儀である。
