【Word VBA】「検索対象が見つからない」恐怖から解放される:無限ループを回避するFindの鉄則
開発現場でWord VBAの`Find`オブジェクトを扱い、頭を抱えた経験はないだろうか。
「文字列を一括置換するマクロを書いたはずが、なぜか処理がフリーズして終わらない」
「特定の見出しを探してループさせたら、同じ場所を延々と回り続ける無限ループに陥った」
Word VBAにおける検索・置換処理は、Excel(Range.Find)とは異なり、独特のステートフルな挙動(検索位置の記憶と文書末尾でのラップ)を持つ。この仕様を正確に理解していないと、実務の現場でシステムが暴走し、ユーザーに多大な迷惑をかけることになりかねない。
今回は、Word VBAの`Find`が持つ挙動の罠を解き明かし、「検索対象が見つからない場合」や「文書の末尾に達した場合」に確実にループを脱出するための堅牢な設計パターンを伝授する。
—
なぜWordのFindは無限ループを引き起こすのか?
初学者や中途半端な知識を持つプログラマが最もやりがちなミスが、以下のような`Do While`ループの構築だ。
‘ 【アンチパターン:絶対に書いてはいけないコード】
Sub BadFindLoop()
Dim rng As Range
Set rng = ActiveDocument.Content
rng.Find.ClearFormatting
rng.Find.Text = “旧ワード”
‘ 見つからなくなるまでループするつもりの甘い設計
Do While rng.Find.Execute
rng.Text = “新ワード”
‘ ここでrngの位置がどう移動しているか意識していない
Loop
End Sub
このコードがなぜ危険なのか。理由は2つある。
1. `Execute`メソッドの副作用
`rng.Find.Execute`が`True`を返した瞬間、`rng`オブジェクトそのものが「見つかった文字列の範囲」に再定義される。そして、検索の起点(ポインタ)は常に「ヒットした文字列の末尾」に移動する。
2. `Wrap`プロパティのデフォルト値
明示的に指定しない場合、検索は文書の末尾に達すると先頭に戻る(`wdFindContinue`)挙動をとることがある。これにより、条件エンティティによっては無限に一周し続ける悪夢が完成する。
—
堅牢なループを実現する3つの鉄則
プロのエンジニアが実務で必ず実装する、無限ループを根絶するための3つの設計思想を明かす。
1. `Wrap`プロパティは必ず `wdFindStop` に明示する
文書の末尾に達した際、勝手に先頭に戻られては困る。検索は「文書の最後まで到達したらそこで終了(Stop)」させなければならない。
2. 検索範囲(Range)の縮小、またはヒット位置の監視
`Execute`が成功した後の`Range`の挙動をコントロールする。置換を行う場合、置換後のテキストの末尾にレンジを移動させ、そこから次の検索を続ける必要がある。
3. 不安要素を排除する明示的な終了条件(Exit Do)
`Execute`の戻り値だけに頼るのではなく、「これ以上検索が進まない」状態をロジカルに検知してループを抜ける構造を作る。
—
【コピペOK】プロダクションコード:安全な一括置換モジュール
それでは、実務の現場でそのまま利用できる、極めて堅牢な文字列置換・検索プロシージャを提示する。エラーハンドリングと、検索がヒットしなかった場合のフェイルセーフも完璧に組み込まれている。
Option Explicit
Public Sub SafeReplaceEngine()
Dim targetDoc As Document
Set targetDoc = ActiveDocument
Dim searchWord As String
Dim replaceWord As String
searchWord = “古い仕様書”
replaceWord = “最新仕様書”
Dim targetRange As Range
Set targetRange = targetDoc.Content
‘ 検索オブジェクトの初期化と安全な設定
With targetRange.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = searchWord
.Replacement.Text = replaceWord
‘ 【重要】ラップさせない(文書末尾に達したら検索を停止する)
.Wrap = wdFindStop
.Forward = True
.Format = False
.MatchCase = True
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
‘ 置換の実行(一括置換 Execute(Replace:=wdReplaceAll) をあえて使わず、
‘ ループ内でログ出力や個別制御を行いたい場合の堅牢な設計)
Dim matchCount As Long
matchCount = 0
‘ 最初の検索実行
Do While .Execute
matchCount = matchCount + 1
‘ ヒットした部分の処理(例:テキストの置換と書式変更)
targetRange.Text = replaceWord
targetRange.Font.Color = wdColorRed ‘ 変更箇所を赤字にするなどの実務的処理
‘ 【極めて重要】
‘ Execute直後の targetRange は「置換された文字列」を指している。
‘ このまま次のループに入ると同じ場所を再検索するか無限ループの原因になるため、
‘ レンジの末尾に移動して検索開始位置を更新する。
targetRange.Collapse wdCollapseEnd
‘ 安全装置:万が一の無限ループを防ぐためのイテレーション制限(必要に応じて)
If matchCount > 10000 Then
MsgBox “安全装置作動:無限ループの可能性があるため強制終了しました。”, vbCritical
Exit Do
End If
Loop
End With
MsgBox “処理完了: ” & matchCount & ” 箇所を置換しました。”, vbInformation
End Sub
—
コードの解説:なぜこの構造が破綻しないのか
上記のコードが実務レベルで信頼できる理由を、アーキテクトの視点から解説する。
1. `targetRange.Collapse wdCollapseEnd` の魔法
これが最大のキモだ。Wordの検索は「指定されたRangeの開始位置、あるいは終了位置」を起点に次を探す。置換が終わった直後の`targetRange`は、その置換されたワード全体を包み込んでいる。`Collapse wdCollapseEnd`を実行することで、レンジを「置換された文字列の右側の点(ゼロ幅のレンジ)」に変形させる。これにより、「すでに置換が終わった場所の後ろ」から正確に次の検索を再開できる。
2. `wdFindStop` による境界防御
万が一、前述のCollapse処理にバグがあったとしても、`Wrap = wdFindStop` が設定されていれば、ドキュメントの終端に達した時点で`Execute`は強制的に`False`を返し、ループは美しく終了する。無限ループの発生源を物理的に断つ盾となる。
—
データベースや外部ファイル連携時の注意点
実務において、このWord VBAの検索エンジンは単体で動くことは少ない。多くの場合、Excelの管理台帳や外部CSV、あるいはSQLデータベースから「検索ワードのリスト」を動的に取得し、それをループさせながらWord文書に適用していくことになる。
その際、以下の鉄則を守ってほしい。
- 外部リストからの入力値は必ずトリム・サニタイズする
Excel等から取得した文字列に意図しない改行コード(`vbCr`や`vbLf`)や全角・半角の揺れが含まれていると、`Find.Text`がヒットせず、想定外のロジックエラーやパフォーマンス低下を招く。
- ドキュメントの保護(Protection)を考慮する
フォーム等で保護されたWord文書に対して検索・置換を行うと、エラー「4605: コマンドは利用できません」が発生する。処理の冒頭で `Unprotect` をかけ、処理終了後に必ず `Protect` で再ロックする堅牢なエラーハンドリング(`On Error Goto ErrorHandler`)をセットで実装すること。
—
まとめ:プロフェッショナルなVBA開発者への道
Word VBAの`Find`オブジェクトは、仕様の癖さえ完全に掌握してしまえば、極めて強力なドキュメント自動化の武器となる。
「なんとなく動くコード」をコピペして運任せにする開発スタイルは、今日で卒業しよう。
「Wrapの指定」「Collapseによるポインタ制御」「明示的なループ脱出条件」。この3つを常にコードの背骨として持たせることで、あなたの書くVBAマクロは、現場で絶対に止まらない「プロフェッショナル・プロダクト」へと昇華する。
仕様の隙を突き、予期せぬバグをあらかじめ論理でねじ伏せる――それこそが、真の業務自動化エンジニアの仕事である。
