【テクニカル・上級編】VB.NETにおけるDo…LoopとWhile…End Whileの使い分け:無限ループを回避し可読性を高めるループ制御の鉄則 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

VB.NETループ制御の極意:`Do…Loop` と `While…End While` の境界線

レガシーなVB6/VBAの時代から現代の.NET 8に至るまで、我々は幾千・幾万ものループ処理を書いてきた。
「とりあえず動くから」と、ファイルを読み込む際やバッチ処理のポーリングに思考停止で `While…End While` を使っていないだろうか? あるいは、すべての条件分岐を `Exit Do` で力技解決していないだろうか。

シニアアーキテクトであれば知っているはずだ。ループの選択ミスは、可読性の低下を招くだけでなく、ストリームの読み込み漏れ、メモリリーク、そして最悪の場合はCPUを100%食い潰す無限ループを引き起こすということを。

本稿では、`Do…Loop` と `While…End While` の本質的な挙動の違いを解剖し、高負荷なファイルI/Oやシステム間連携バッチにおいて、いかにして安全かつ高速なループを構築するか、その鉄則を叩き込む。

1. 構造的差異:前置判定と後置判定のパラダイム

まず、両者の言語仕様における決定的な違いを整理する。

  • `While…End While`: 厳格な前置判定(Pre-test)ループ。
  • `Do…Loop`: 前置判定(`Do While/Until…Loop`)および後置判定(Post-test)(`Do…Loop While/Until`)の両方をサポートするハイブリッド構文。

この「判定が先か、実行が先か」という違いは、特に外部リソース(ファイル、DBカーソル、ネットワークストリーム)を扱う場面で生死を分ける。

比較表:どちらを使うべきか

| 状況 | 推奨構文 | 理由 |
| :— | :— | :— |
| 0回以上の実行(条件次第で一度も走らない) | `While…End While` または `Do While…Loop` | 安全性重視。無駄な初期化を防ぐ。 |
| 1回以上の実行(少なくとも一度は処理を通す) | `Do…Loop While`(後置判定) | 「処理して、まだ続けるか確認する」という人間の直感に合致する。 |
| 複雑な脱出条件・無限ループ監視 | `Do…Loop` + 内部 `Exit Do` | 複数条件や例外的なブレイクポイントを制御しやすい。 |

2. 実践:ファイルストリーム読み込みにおける「罠」

巨大なログファイルやバイナリデータを `StreamReader` で読み込むバッチ処理を想像してほしい。よく見かけるアンチパターンとして、`While…End While` を使って無理にコードをねじ曲げているケースがある。

アンチパターン:不自然な `While` の二重評価

.net
‘ 【悪手】無理やり While で書いた例
Using reader As New StreamReader(“C:\Logs\massive.log”, Encoding.UTF8)
Dim line As String = reader.ReadLine()

‘ ループに入る前の事前読み込みが必要になり、コードが冗長化する
While line IsNot Nothing
Console.WriteLine(line)
‘ 次の行を読むためにループの末尾でも再び呼ぶ必要がある
line = reader.ReadLine()
End While
End Using

この書き方は、ループの「外」と「中」で同じ `ReadLine()` を呼び出す必要があり、保守性が最悪だ。メンテナンス時に片方を消し忘れて無限ループの餌食になったエンジニアを、私は何人も見てきた。

模範解答:後置判定 `Do…Loop` によるエレガントなストリーム処理

ストリーム読み込みは、「とりあえず1回読んで、データがあれば処理を続け、EOF(終端)に達したら抜ける」という後置判定の典型ユースケースである。

.net
‘ 【正解】Do…Loop While を使った極限まで洗練されたストリーム処理
Using reader As New StreamReader(“C:\Logs\massive.log”, Encoding.UTF8)
Dim line As String
Do
line = reader.ReadLine()
If line IsNot Nothing Then
‘ 実際のビジネスロジック・パース処理
ProcessLogLine(line)
End If
Loop While line IsNot Nothing
End Using

これにより、読み込みロジックがループ内にカプセル化され、可読性が劇的に向上する。

3. 無限ループの回避と `Exit Do` の正しい作法

バッチ処理や外部APIのポーリング(状態監視)では、意図的な無限ループ(`Do…Loop`)を多用する。しかし、安全装置なき無限ループはサーバーをクラッシュさせる時限爆弾だ。

プロフェッショナルなコードでは、以下の3原則を遵守する。

1. タイムアウト機構(TickCount / Stopwatch)の実装
2. 安全な脱出条件(`Exit Do`)の明文化
3. CPUリソースの解放(`Thread.Sleep` の適切な配置)

実装例:安全なポーリング処理(API連携バッチ)

.net
Imports System.Threading
Imports System.Diagnostics

Public Sub PollExternalService()
Const MaxTimeoutMs As Integer = 30000 ‘ 30秒でタイムアウト
Dim sw As Stopwatch = Stopwatch.StartNew()

‘ 条件なし Do Loop による無限ループの構築
Do
‘ 1. 外部APIの状態をチェック
Dim isReady As Boolean = CheckServiceStatus()

If isReady Then
Console.WriteLine(“サービスが応答しました。処理を継続します。”)
Exit Do ‘ 正常脱出
End If

‘ 2. タイムアウト監視(無限ループ暴走の防止)
If sw.ElapsedMilliseconds > MaxTimeoutMs Then
Throw New TimeoutException(“外部サービスの応答がタイムアウトしました。”)
End If

‘ 3. CPU使用率のスパイクを防ぐため、スレッドを休止させる
‘ (※ Sleepを入れないとCPUコアが100%張り付く致命傷になる)
Thread.Sleep(1000)

Loop

sw.Stop()
‘ 後続処理へ…
End Sub

Private Function CheckServiceStatus() As Boolean
‘ ダミーのステータスチェック処理
Return New Random().Next(0, 5) = 3 ‘ 1/5の確率でTrue
End Function

4. レガシー保守の知見:VB.NETにおけるメモリとオブジェクトのライフサイクル

ここで、レガシーシステム(.NET Framework 3.5 / 4.xなど)を保守するエンジニアに向けた、一歩踏み込んだ知見を共有する。

大量のデータをループ内で処理する場合、GC(ガベージコレクション)のプレッシャーがパフォーマンスを殺す。特にVB.NETのループ内での文字列結合や、マネージドリソースの破棄漏れは致命的だ。

鉄則:ループ変数とインスタンスのスコープ最適化

.net
‘ 【悪い例】ループ内で毎回インスタンス生成と破棄を繰り返し、GCの負荷を高める
Do While reader.Peek() >= 0
Dim parser As New HeavyParser() ‘ ループ内の毎回のNewはゴミの山を生む
parser.Parse(reader.ReadLine())
Loop

‘ 【良い例】インスタンスをループ外で再利用(オブジェクトプーリングの思想)
Dim parser As New HeavyParser()
Do While reader.Peek() >= 0
‘ 状態をリセットして使い回す
parser.Reset()
parser.Parse(reader.ReadLine())
Loop
‘ 必要に応じて Using や明示的 Dispose

また、COMオブジェクト(Excel操作など古いVBAからの移行期にありがちな `Microsoft.Office.Interop.Excel` 等)をVB.NETのループ内で扱う場合は、ループの1イテレーションごとに確実に参照を解放(`Marshal.ReleaseComObject`)しなcurrentItem、メモリリークの温床となる。マネージドの世界であっても、外部リソースのライフサイクル管理に対するシビアな視点は忘れてはならない。

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

ループ構文の選択は、単なる「好み」の問題ではない。それはコードの意図を後続のエンジニアに正確に伝えるための言語契約(Language Contract)である。

  • 条件が満たされている間だけ回すなら `While…End While`
  • 最低1回は必ず処理を通す、あるいは複雑な脱出制御を伴うなら `Do…Loop`

この基本原則を胸に刻み、CPUとメモリに優しく、かつ誰が見ても一瞬で意図が理解できる堅牢なコードベースを築き上げてほしい。プロの仕事とは、こういう細部へのこだわり宿るところにしか存在しないのだから。

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