制御不能な再帰は、プロダクション環境における「時限爆弾」である
業務自動化の現場において、多くの開発者が陥る罠がある。それは、「再帰処理(Recursion)こそがエレガントな解決策である」という盲信だ。
フォルダ階層を探索する際、直感的に「自分自身を呼び出す」再帰処理を書きたくなる気持ちはわかる。しかし、考えてみてほしい。もし対象のディレクトリがネットワークドライブ上にあり、数百万のファイルや、意図しない循環参照(シンボリックリンク)が含まれていたらどうなるか? スタックオーバーフローを引き起こし、システムのリソースを食いつぶし、最悪の場合、サーバーの応答を停止させる。
プロフェッショナルが求めるのは「エレガントなコード」ではない。「予測可能で、堅牢で、制御可能なコード」だ。
今回は、FileSystemObject (FSO) を用い、再帰の暴走を抑え込み、指定した深さ(階層レベル)だけで確実にフォルダを列挙する、実務に耐えうる設計思想と実装法を伝授する。
1. 設計思想:なぜ「深さ」を制御しなければならないのか
FSOの `GetFolder().SubFolders` は非常に強力だが、無計画に使うと制御不能になる。実務的な自動化ツールにおいて、階層制限が必要な理由は主に3つある。
1. パフォーマンスの最適化: 不要な深層ディレクトリへのスキャンを避け、I/O負荷を最小限に抑える。
2. 権限エラーの回避: 深い階層には、一般ユーザーがアクセスできないシステム領域や保護されたフォルダが潜んでいる。
3. 業務ロジックの整合性: 「プロジェクトフォルダ直下の各クライアントフォルダ」だけを処理したい場合、その下のさらに深いサブフォルダまで処理してしまうと、データの重複や誤操作を招く。
設計の鉄則は、「現在の深さ(Current Depth)」と「許容される最大深さ(Max Depth)」を常に変数として保持し、それを境界条件として扱うことだ。
2. 実装パターン:直下のみ vs 指定階層まで
実装には大きく分けて2つのアプローチがある。
パターンA:直下(1階層目)のみを対象とする「非再帰型」
これは最もシンプルで、最も安全な方法だ。ループを1回回すだけで完結するため、スタックを消費しない。
パターンB:指定した深さまで探索する「制御付き再帰型」
「2階層目まで」「3階層目まで」といった柔軟な要件にはこれを用いる。ただし、前述の通り「深さのカウント」を引数で渡し、上限に達した瞬間に探索を打ち切る設計にする必要がある。
3. プロダクション・コード:堅牢なフォルダ列挙スクリプト
以下に、実務でそのまま利用可能な、エラーハンドリングと深さ制御を組み込んだコードを示す。このコードは、単にリストアップするだけでなく、アクセス拒否(Permission Denied)などのランタイムエラーに対処する設計になっている。
'==============================================================================
' Script Name: DepthLimitedFolderEnumerator.vbs
' Description: 指定した階層レベルまでのフォルダを安全に列挙する
' Author: Lead Architect
'==============================================================================
Option Explicit
' --- 設定セクション ---
Const TARGET_ROOT_PATH = "C:\Your\Target\Path" ' 探索開始ルート
Const MAX_DEPTH = 2 ' 探索する最大階層 (1=直下のみ, 2=直下+その下のフォルダ)
' ---------------------
Dim fso, rootFolder
Set fso = CreateObject("Scripting.FileSystemObject")
' メインエントリーポイント
Call Main()
Sub Main()
On Error Resume Next
' ルートパスの存在確認
If Not fso.FolderExists(TARGET_ROOT_PATH) Then
WScript.Echo "Error: 指定されたルートパスが存在しません: " & TARGET_ROOT_PATH
Exit Sub
End If
Set rootFolder = fso.GetFolder(TARGET_ROOT_PATH)
WScript.Echo "--- 探索開始: " & TARGET_ROOT_PATH & " (最大深さ: " & MAX_DEPTH & ") ---"
' 探索開始(現在の深さを1として開始)
Call EnumerateFolders(rootFolder, 1)
WScript.Echo "--- 探索完了 ---"
' オブジェクトの解放
Set rootFolder = Nothing
Set fso = Nothing
End Sub
''' <summary>
''' 指定した深さまでフォルダを再帰的に探索する
''' </summary>
''' <param name="currentFolder">現在探索中のフォルダオブジェクト</param>
''' <param name="currentDepth">現在の階層レベル</param>
Sub EnumerateFolders(currentFolder, currentDepth)
Dim subFolder, folderItem
' 1. 現在のフォルダを表示(インデントで階層を可視化)
WScript.Echo String(currentDepth * 2, " ") & "|-- " & currentFolder.Name
' 2. 最大深度に達している場合は、これ以上深く潜らない
If currentDepth >= MAX_DEPTH Then
Exit Sub
End If
' 3. サブフォルダの列挙
' FSOの操作では、アクセス権限がないフォルダに触れた瞬間にエラーが発生するため
' ループ内でのエラーハンドリングが極めて重要
On Error Resume Next
For Each subFolder In currentFolder.SubFolders
If Err.Number <> 0 Then
WScript.Echo String(currentDepth * 2, " ") & "[Error: Access Denied or Folder Corrupted]"
Err.Clear
Else
' 再帰呼び出し:深さをインクリメントして渡す
Call EnumerateFolders(subFolder, currentDepth + 1)
End If
Next
If Err.Number <> 0 Then
WScript.Echo "[Critical Error in SubFolders collection: " & Err.Description & "]"
Err.Clear
End If
On Error GoTo 0
End Sub
4. アーキテクトの視点:実務での注意点
このスクリプトを業務ツールに組み込む際、以下の3点に留意せよ。
① エラーハンドリングの「質」
コード内で `On Error Resume Next` を使用しているが、これは「エラーを無視するため」ではなく、「エラーを検知し、適切に処理(スキップまたはログ出力)して、処理を継続させるため」に存在している。アクセス権限のないフォルダに遭遇した際、スクリプト全体を停止させるのは、自動化ツールとしては「失格」だ。
② I/O負荷とログ出力
`WScript.Echo` はデバッグには良いが、数千のフォルダを処理する場合、コンソール出力自体がボトルネックになる。プロダクション環境では、結果を `FileSystemObject` を使って CSVファイルやテキストログに書き出す 設計に変更すべきだ。
③ オブジェクトのライフサイクル
VBScriptはCOMオブジェクトを利用している。ループの中で大量のフォルダオブジェクトを生成する場合、メモリリークを防ぐために、不要になったオブジェクトは明示的に `Nothing` で解放する習慣をつけろ。今回のコードのように、再帰のスタックから戻る過程でスコープが外れる設計にすることで、管理を容易にしている。
結論
「動くコード」を書くのは誰にでもできる。しかし、「止まらないコード」「予測できるコード」を書けるのが、真のエンジニアだ。
`GetFolder.SubFolders` を扱うときは、常に「深さ」という概念をコントロール下に置け。 それが、君の作成する自動化ツールを、脆弱なスクリプトから、信頼されるシステムへと昇華させる唯一の道である。
