【テクニカル・上級編】【中級者向け】「検索対象が見つからない」場合に発生する無限ループを回避する終了判定の定石 – Word VBA解析バイブル

スポンサーリンク

【Word VBA】無限ループの悪夢を断つ:Findオブジェクトの正確な終了判定と堅牢な実装パターン

Word VBAにおけるテキスト処理の自動化において、`Find` オブジェクトと `Replacement` オブジェクトの操作は避けて通れない領域だ。数万行に及ぶドキュメントの一括置換やメタデータの抽出において、これらを完璧に使いこなすことはシニアエンジニアの必須スキルと言える。

しかし、現場のコードレビューを行っていると、いまだに `Do Until` や `Do While` のループ条件を誤り、「検索対象が見つからない」あるいは「文書の末尾に達した」瞬間から無限ループに突入する地雷コードに遭遇する。CPU使用率が100%に張り付き、Wordが強制終了するあの絶望的な瞬間を、君も一度は経験したことがあるはずだ。

今回は、Word VBAの `Find` 機構におけるオブジェクトのライフサイクルと、暴走を完全に封じ込めるための「定石」を、チーフアーキテクトの視点から徹底的に解説する。

—

1. なぜWordのFindは無限ループを引き起こすのか?

Excelの `Range.Find` と比較して、Wordの `Selection.Find` または `Range.Find` は、その内部状態(State)の持ち方が極めて特殊である。

Wordの検索エンジンは、文書の最後まで検索が進むと、デフォルトの設定(`Wrap = wdFindContinue`)では文書の先頭に戻って検索を継続しようとする。この挙動を制御下に入れず、単に「ヒットしなくなるまでループする」という素朴なロジックを書くと、以下のような致命的な罠に落ちる。

1. 検索文字列が文書内に存在する。
2. ループ内で置換や処理が行われる。
3. 文書末尾に達し、`Wrap` の設定によって検索位置が文書先頭へラップアラウンドする。
4. 再び最初に見つかった文字列をヒットさせ、永遠に終了条件を満たさない。

この暴走を防ぐためには、「検索が文書の末尾を超えて一周したこと」をプログラム側で検知するか、あるいは検索のラップを禁止して明示的に終了を判定するかしなければならない。

—

2. 破綻しないための設計思想:Wrapプロパティの制御

無限ループを回避するためのアプローチは大きく分けて2つある。

  • アプローチA:ラップを禁止し(`wdFindStop`)、ヒットしなくなった時点でループを抜ける(最も安全で確実)
  • アプローチB:文書全体を巡回し、検索位置が元の位置に戻った、または逆転したことを検知する

実務の現場において、特別な理由がない限り 「アプローチA(`Wrap = wdFindStop`)」 を採用すべきだ。文書を循環させる必要性があるのは、複数回のパスを回す特殊なパーサーを書く場合ごく一部に限られる。

—

3. 実装コード:極限まで洗練された堅牢な置換パターン

以下に、実務の現場でそのまま利用できる、オブジェクトのライフサイクルを考慮した堅牢なファンクションプロシージャを示す。

メモリリークや予期せぬCOM例外を防ぐため、`Range` オブジェクトを適切に取得・解放し、`Find` プロパティを一元管理している点に注目してほしい。

Option Explicit

Public Sub SafeExecuteFindAndReplace(ByVal targetDoc As Document, ByVal findText As String, ByVal replaceText As String)
‘ ——————————————————————————–
‘ 堅牢な検索・置換実行プロシージャ
‘ ——————————————————————————–
Dim rngTarget As Range
Dim isFound As Boolean

‘ ドキュメント全体をカバーするRangeオブジェクトを取得
Set rngTarget = targetDoc.Content

‘ 検索条件の初期化と厳格な設定
With rngTarget.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = findText
.Replacement.Text = replaceText

‘ 【最重要】ラップを禁止する。これにより文書末尾での無限ループを物理的に根絶する。
.Wrap = wdFindStop

.Forward = True
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False

‘ 実行と終了判定のループ
‘ .Executeメソッド自体がBooleanを返すため、これを利用してループを制御する
isFound = .Execute(Replace:=wdReplaceAll)
End With

‘ 実行結果のログ出力(システム間連携やバッチ処理を想定した設計)
If isFound Then
Debug.Print “Success: ‘” & findText & “‘ を ‘” & replaceText & “‘ に置換しました。”
Else
Debug.Print “Notice: ‘” & findText & “‘ は見つかりませんでした。”
End If

CleanUp:
‘ オブジェクトの明示的解放(VBAのガベージコレクション挙動に依存しないプロフェッショナルな実装)
Set rngTarget = Nothing
End Sub

このコードのアーキテクチャ的優位性

1. `wdReplaceAll` の内部ループ活用
Word VBAの `.Execute(Replace:=wdReplaceAll)` は内部でネイティブのC++層でループを完結させるため、VBAの `Do` ループを回すよりも圧倒的に高速であり、VBA層での無限ループリスクを完全に排除できる。
2. `Wrap = wdFindStop` の徹底
検索が文書末尾に達した時点で処理が確実に停止するため、予期せぬ再検索を防ぐ。
3. 明示的なメモリ管理
処理の最後で `Set rngTarget = Nothing` を実行し、WordのCOMコンテキストにおけるメモリリークを防ぐ。特に長大文書を扱うバッチ処理では、この数行の有無が安定性を大きく左右する。

—

4. より複雑な条件:手動で `Do` ループを回す場合の終了判定

一括置換(`wdReplaceAll`)ではなく、ヒットした箇所ごとに前後の文脈を解析して処理を分岐させるなど、どうしても `Do` ループ内で `.Execute` を呼び出す必要がある場合の定石コードを提示する。

Public Sub AdvancedIterativeSearch(ByVal targetDoc As Document, ByVal searchKeyword As String)
Dim rngSearch As Range
Set rngSearch = targetDoc.Content

With rngSearch.Find
.ClearFormatting
.Text = searchKeyword
.Forward = True
.Wrap = wdFindStop ‘ 繰り返し処理であってもラップは原則禁止
.MatchCase = True
End With

‘ 検索実行とループ
Do While rngSearch.Find.Execute
‘ — ここにヒット時のカスタム処理を記述 —
‘ 例: ヒットした段落のスタイルを変更する
rngSearch.Paragraphs(1.0).Style = “Heading 1”

‘ 【重要】
‘ Find.Executeが成功すると、rngSearch自体が「ヒットした文字列の範囲」に縮小(Collapse)または移動する。
‘ 次の検索を開始するためには、範囲の末尾にレンジを移動させてから検索を再開する必要がある。
‘ これを行わないと、同一箇所を無限に指し続けることになる。
rngSearch.Collapse wdCollapseEnd

Loop

CleanUp:
Set rngSearch = Nothing
End Sub

ループ制御の核心:`Collapse` の意味

手動ループを回す際に見落とされがちなのが、`rngSearch.Collapse wdCollapseEnd` の存在だ。
`Find.Execute` が成功すると、`rngSearch` オブジェクトの参照範囲は「ヒットしたキーワードそのもの」に書き換わる。そのまま次の `.Execute` を呼ぶと、Wordは「現在の選択範囲内(つまりヒットした直後の文字列)」を再度スキャンしようとし、最悪の場合、同一箇所でスタックするか予期せぬ挙動を引き起こす。

ヒットした位置の「末尾」にレンジを折りたたむ(Collapseする)ことで、検索開始位置を「前回ヒットした箇所の直後」へと正しく進めることができるのだ。

—

5. シニアエンジニアからの提言

レガシーなVBAコードの多くは、動けば良いという発想で書かれており、エッジケース(検索対象が存在しない、文書が保護されている、巨大すぎる等)に対する耐性が欠落している。

今回解説した `Wrap = wdFindStop` の徹底と、`Collapse` による検索カーソルの適正化は、単なる「テクニック」ではなく、システムを安定稼働させるための防衛的プログラミングの基本原則である。

「なぜか時々固まるWordマクロ」に悩まされているなら、今すぐ既存のコードベースの `Find` 実装を見直し、ラップ設定と終了条件のロジックを検証してほしい。プロフェッショナルたるもの、予期せぬ暴走を許さない堅牢なアーキテクチャを常に構築し続けなければならない。

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