【実務・中級編】【スタックオーバーフロー回避】VBScriptの再帰呼び出し限界と反復ループ(イテレーション)への安全変換ロジック – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【スタックオーバーフロー回避】VBScriptの再帰呼び出し限界と反復ループへの安全変換ロジック

業務自動化の現場において、VBScriptとWSH(Windows Script Host)はいまだにレガシー環境のキーストロークを支える縁の下の力持ちだ。Active Directoryの操作、複雑なファイルサーバーのディレクトリ走査、あるいは日報のバッチ処理など、インフラの根幹で静かに稼働し続けている。

しかし、この枯れた言語を扱う上で、多くの開発者が直面し、そして沈黙する恐怖のエラーがある。

> 「スタック領域が不足しています (Out of stack space)」(エラー番号: ꞃ028)

ディレクトリの階層が想定以上に深かったり、XML/JSONのパース処理で自己参照(再帰)を多用したりした瞬間、VBScriptの実行エンジン(`wscript.exe` / `cscript.exe`)は音を立ててクラッシュする。

今回は、VBScriptにおけるコールスタックの限界の正体を解き明かし、安全な「明示的スタック・キュー構造」を用いた反復ループへの変換ロジックを、実務でそのまま使えるプロダクションコードとともに伝授する。

1. なぜVBScriptの再帰は「爆弾」なのか?

コールスタックの物理的限界

現代の高水準言語(C#, Python, Javaなど)や、ガベージコレクションとJIT最適化を持つモダンな環境では、ある程度の再帰呼び出しは耐えられる。しかし、VBScriptの裏で動いているCOMコンポーネントおよびスクリプトエンジンは、非常に限られたメモリ空間(特に32プロセスとしての制約)で動作している。

VBScriptで関数が自分自身を呼び出す(再帰)たびに、以下のデータがコールスタックに積み上げられる。

  • 戻りアドレス
  • ローカル変数
  • 引数のコピー

VBScriptのデフォルトのコールスタックサイズは、OSのメモリ割り当てやWSHの制限により、数千回(場合によっては数百回)のネストで容易に上限を迎える。特に、「予測不可能な深さを持つツリー構造(ファイルシステムなど)」の走査に再帰を使うのは、時限爆弾を抱えているのと同じである。

再帰の罠:エラーハンドリングの無力化

VBScriptの `On Error Resume Next` さえも、スタックオーバーフロー(Out of stack space)の前では無力な場合が多い。このエラーが発生すると、スクリプトは制御を失い、容赦なく異常終了する。バッチ処理の途中でこれが起きれば、ログすら残さずにプロセスが消滅する悪夢を見る事になる。

2. 解決策:再帰から「明示的スタック/キューによる反復」への脱却

安全なコードを書くための鉄則はシンプルだ。
「システム任せのコールスタック(暗黙的)を捨て、自前で配列やコレクションを使ったスタック(明示的)を実装する」

  • LIFO (Last In, First Out) スタック: 深さ優先探索(DFS)を反復で行う場合に使用する。再帰関数の置き換えに最適。
  • FIFO (First In, First Out) キュー: 幅優先探索(BFS)を行う場合に使用する。

ここでは、実務で最も需要が高い「深層ファイルシステムの全件走査」を題材に、再帰関数から明示的スタックを用いた反復ループへの華麗なトランスフォーメーションを実演しよう。

3. 実装例:安全なディレクトリ走査スクリプト

以下のコードは、数万ファイルの階層構造があってもスタックオーバーフローを起こさない、堅牢なVBScriptのプロダクションコードである。動的配列をスタックとしてエミュレートしている。

‘ =================================================================0
次元を超えた堅牢なファイルシステム走査スクリプト
用途: 指定ルート配下の全ファイル・フォルダを安全に列挙し、ログ出力する
特徴: 再帰呼び出しを完全排除し、独自スタックによる反復処理でメモリ枯渇を防止
===================================================================
Option Explicit

Sub TraverseDirectorySafe(ByVal rootPath)
Dim fso, targetFolder, subFolders, file
Dim stack() ‘ 明示的スタックとして使用する動적配列
Dim stackPointer
Dim currentFolder

Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ ルートパスの存在確認
If Not fso.FolderExists(rootPath) Then
WScript.Echo “Error: 指定されたパスが存在しません -> ” & rootPath
Exit Sub
End If

‘ スタックの初期化 (初期サイズ 100、動的に拡張)
ReDim stack(100)
stackPointer = 0

‘ 初期フォルダーをスタックに積む
Set stack(stackPointer) = fso.GetFolder(rootPath)
stackPointer = stackPointer + 1

WScript.Echo “=== 走査開始: ” & rootPath & ” ===”

‘ スタックが空になるまでループ (LIFO: 深さ優先探索)
Do While stackPointer > 0
‘ スタックからポップ (取り出し)
stackPointer = stackPointer – 1
Set currentFolder = stack(stackPointer)

‘ — 障害耐性: アクセス権限エラー等の対策 —
On Error Resume Next
Set subFolders = currentFolder.SubFolders
If Err.Number <> 0 Then
‘ アクセス拒否などのフォルダーはスキップして継続
WScript.Echo “[警告] アクセススキップ: ” & currentFolder.Path & ” (Error: ” & Hex(Err.Number) & “)”
Err.Clear
Set currentFolder = Nothing
GoTo ContinueLoop
End If
On Error GoTo 0

‘ 1. 現在のフォルダー内のファイルを処理(業務ロジック)
‘ ※実際の業務ではここでDBへの書き込みやファイル操作を行う
For Each file In currentFolder.Files
‘ WScript.Echo “File: ” & file.Path
Next

‘ 2. サブフォルダーをスタックに積む
For Each targetFolder In subFolders
‘ スタック配列のオーバーフロー防衛と動的拡張
If stackPointer > UBound(stack) Then
ReDim Preserve stack(UBound(stack) + 100)
End If

Set stack(stackPointer) = targetFolder
stackPointer = stackPointer + 1
Next

ContinueLoop:
Loop

WScript.Echo “=== 走査完了 ===”
Set fso = Nothing
End Sub

‘ 実行エントリポイント
Dim targetDir
targetDir = “C:\Windows” ‘ テスト用に大きめのディレクトリを指定
TraverseDirectorySafe targetDir

4. プロフェッショナルのコード解説:なぜこの設計なのか?

① 動的配列によるメモリ管理 (`ReDim Preserve`)

VBScriptの配列はパフォーマンスやメモリの観点でクセがあるが、 `stackPointer` をインデックスとして管理する自前スタック構造を作れば、関数呼び出しのオーバーヘッドをほぼゼロに抑えられる。
さらに、配列の限界 (`UBound`) を超えた瞬間に `ReDim Preserve` で100要素ずつ拡張するアルゴリズムを採用しているため、メモリの無駄な消費を防ぎつつ、無限に近い階層を耐え抜くことができる。

② 例外の局所化 (`On Error Resume Next` の正しい使い方)

巨大なネットワーク共有フォルダやシステムフォルダーを走査する際、権限不足 (`Permission Denied`) でクラッシュすることは日常茶飯事だ。
このコードでは、`SubFolders` を取得するクリティカルな部分だけにエラーハンドリングを挟み、アクセスできないフォルダーに出くわしてもスクリプト全体を止めずに「スキップして次へ進む」タフな設計にしている。

③ コールバック・ロジックの分離

ファイルを見つけた際の処理(`For Each file In currentFolder.Files` の部分)を独立させることで、ファイル検索エンジンとしての再利用性が劇的に高まる。ここにCSV出力やデータベース(ADODB)へのバルクインサート処理を組み込めば、そのまま堅牢な業務バッチツールが完成する。

5. チーフアーキテクトからの提言

VBScriptやWSHは「古い技術」として片付けられがちだが、OSの標準機能だけで動作し、インストーラー不要で即座に現場の自動化を形にできるという圧倒的なアドバンテージを持っている。

しかし、その手軽さゆえに、安易な再帰呼び出しのような「動けばいい設計」を持ち込むと、運用フェーズで原因不明の停止を引き起こす。
「スタックはシステムに頼らず、自分で管理せよ」。この原則を胸に刻むだけで、あなたの書くVBScriptは、プロフェッショナルが認める「止まらない自動化スクリプト」へと生まれ変わる。

現場の信頼を勝ち取るために、今日からあなたのコードの再帰処理をすべて「明示的スタックループ」へとリファクタリングしてほしい。

タイトルとURLをコピーしました