VBA Do Loopの使い方|4パターンの選び方と無限ループ対策

スポンサーリンク

「このデータ、何行あるか毎回変わるんだよな……」

Excel VBA で繰り返し処理を書いていると、必ずこの壁にぶつかりますよね。For文は「1から10まで」のように回数を先に決める書き方。行数が日によって変わるデータだと、終了値に書く数字が決められません。

かといって毎回シートを開いて行数を数え、マクロの数字を書き換えるのは面倒ですよね。データが増えるたびに手直しが必要なマクロは、そのうち誰も使わなくなってしまうもの。

そこで出番になるのが VBA の Do Loop です。「A列が空になるまで」「累計が100万円を超えるまで」といった書き方ができます。終わりを回数ではなく条件で決められるのが、このステートメント最大の強み。

この記事では、次の4つをコピペで動くコードとセットで解説していきます。

  • For文・For Each との使い分け(結論から先にお伝えします)
  • 4パターン(While / Until × 前判定 / 後判定)の選び方
  • 実務でそのまま使えるコード4本
  • 無限ループの止め方・防ぎ方・抜け方

対応バージョン: Do Loop は Excel 2010 以降のデスクトップ版 Excel で共通に使えます。バージョンによる挙動差もありません。ただし Excel for the web(ブラウザ版)は VBA 非対応です。ブラウザ版ではマクロそのものを実行できない点にご注意ください。

Do LoopとFor文の使い分け(結論先出し)

Do Loopは「終わる回数が決まっていない繰り返し」に使う

Excel VBA の繰り返しステートメントは3種類あります。まずは結論から言ってしまいますね。

繰り返す回数が事前に分からないなら Do Loop。分かっているなら For系。 判断基準はこれだけ。

Do Loop は回数ではなく条件でループの継続と終了を決めます。1周ごとに条件式が True か False かをチェックし、その結果で「もう一周するか」を判断する仕組み。だから終了タイミングがセルの中身やユーザー操作に左右される処理でも書けるわけです。

たとえば次のような処理は、For文では素直に書けません。

  • A列を上から読んで、空白セルに当たったら止めたい
  • 売上を順に足して、累計が目標額を超えた行を知りたい
  • InputBox で「終了」と入力されるまで受け付け続けたい
  • フォルダ内のファイルを、なくなるまで1つずつ処理したい

どれも「何回繰り返すか」が実行してみるまで確定しないケース。回数が先に決まっていないので、For文の終了値に書く数字がそもそも存在しないんですね。

逆に「1行目から10行目まで」のように回数が確定しているなら、無理に Do Loop を使う必要はありません。カウンターが自動で進む For文のほうが安全ですし、コードも短く済みます。

For~Next / For Each / Do Loop の違いを比較表で整理

3つのステートメントの違いを1枚の表にまとめました。

比較項目For ~ NextFor Each ~ NextDo Loop
繰り返しの基準回数(開始値〜終了値)コレクションの全要素条件(True / False)
回数は事前に分かるか分かる実行時に自動で決まる分からないことが多い
カウンター変数必須(自動で進む)不要自分で用意して自分で進める
典型的な用途2〜11行目の処理、連番の生成全シート、選択範囲の全セル空白まで、目標値まで、入力終了まで
途中で抜ける命令Exit ForExit ForExit Do
無限ループの危険ほぼなしほぼなしあり(条件・カウンター次第)

表に出てくる「コレクション」とは、同じ種類のオブジェクトをまとめて扱う入れ物のこと。ワークシートの集まりやセル範囲がその代表例です。全シートを順番に処理したいなら、この形が一番短く書けますよ。

使い分けの目安は次の3行に集約できます。

  • 回数が決まっている → For~Next文
  • 全シート・全セルなど集合をまるごと → For Each~Next文
  • 条件で終わりが決まる → Do Loop

注目してほしいのが表の最終行。無限ループの危険が「あり」なのは Do Loop だけなんです。For系はカウンターが自動で進むので、必ず終わりが来ます。

一方の Do Loop は、カウンターを進めるのも条件を変化させるのも書き手の責任。ここが後半のテーマにつながります。

NOTE

コードを書く場所は VBE(Visual Basic Editor)です。Excel の画面で Alt + F11 を押すと起動します。次に「挿入」→「標準モジュール」を選び、右側の白い領域にコードを貼り付けてください。実行は F5 キーです。

書いたマクロを残すには、ファイルを マクロ有効ブック(.xlsm) で保存する必要があります。画面の見方に自信がない方は、VBE画面の見方を先に確認しておくと安心ですよ。

Do Loopの4パターンを動かして理解する

Do Loop には WhileUntil という2つのキーワードがあります。さらに条件判定をループの先頭に置くか末尾に置くかで、合計4パターンの書き方になります。

先に用語を整理しておきましょう。判定をループの先頭に置く形を前判定と呼びます。ループに入る前に条件をチェックする形ですね。

判定を末尾に置く形が後判定。中身を1回実行してから条件をチェックします。

この違いが「最低何回実行されるか」を左右します。ここが4パターンの肝なので、実際に動かしながら確認していきましょう。

Do While … Loop(前判定・満たす間)

条件を満たしている間、処理を繰り返す形です。判定するのはループに入る前。

Do While 条件式
    '--- 繰り返す処理 ---
Loop

1から3まで数える最小コードで動きを見てみましょう。

Sub DoWhileLoop_Basic()
    Dim i As Long 'カウンター
    i = 1

    Do While i <= 3
        Debug.Print i & "回目の処理"
        i = i + 1
    Loop

    Debug.Print "ループ終了(i は " & i & ")"
End Sub

実行結果はイミディエイトウィンドウに出力されます。VBE で Ctrl + G を押すと開く、結果確認用のウィンドウのこと。出力は次のとおりです。

1回目の処理
2回目の処理
3回目の処理
ループ終了(i は 4)

i が4になった瞬間に i <= 3 が False となり、ループを抜けます。ポイントは i = i + 1 の行。これを書き忘れると i が永遠に1のままになり、無限ループへ一直線です。

そして前判定の最大の特徴がこれ。条件が最初から False なら、中身は1回も実行されません。 上のコードで i = 10 から始めれば、出力は「ループ終了(i は 10)」の1行だけになります。

Do ... Loop While(後判定・満たす間)

処理を1回実行したあとに条件を判定する形です。条件を満たしている間は繰り返します。

Do
    '--- 繰り返す処理 ---
Loop While 条件式

先ほどと同じ「1から3まで」を後判定で書いてみます。

Sub DoLoopWhile_Basic()
    Dim i As Long 'カウンター
    i = 1

    Do
        Debug.Print i & "回目の処理"
        i = i + 1
    Loop While i <= 3

    Debug.Print "ループ終了(i は " & i & ")"
End Sub

出力は前判定版とまったく同じ4行になります。i = 1 からスタートする限り、両者に差は出ません。

違いが表に出るのは、条件を最初から満たしていないときです。i = 10 から始めると、後判定版は「10回目の処理」を1回だけ出力します。判定より先に中身を実行してしまうからですね。

必ず1回は実行したい処理には、この形がぴったりです。たとえば「入力ダイアログを最低1回は出す」といった場面ですね。

Do Until ... Loop(前判定・満たすまで)

条件を満たすまで処理を繰り返す形です。判定の位置は前判定と同じで、意味だけが反転します。

Do Until 条件式
    '--- 繰り返す処理 ---
Loop

While との対応関係を押さえておきましょう。「i <= 3 である」と「i > 3なるまで」は、まったく同じ振る舞いになります。

Sub DoUntilLoop_Basic()
    Dim i As Long 'カウンター
    i = 1

    Do Until i > 3
        Debug.Print i & "回目の処理"
        i = i + 1
    Loop

    Debug.Print "ループ終了(i は " & i & ")"
End Sub

出力は Do While 版と同一の4行です。条件式を否定形にひっくり返しただけ、というわけ。

While と Until のどちらを選ぶかは、条件式が自然に読めるほうで決めて構いません。

「空白でない間」なら Do While Cells(i, 1).Value <> ""。「空白になるまで」なら Do Until Cells(i, 1).Value = ""。読みやすいほうを選んでみてください。

Do ... Loop Until(後判定・満たすまで)

処理を1回実行したあとに「条件を満たしたか」を判定する形です。4パターンの最後の1つ。

Do
    '--- 繰り返す処理 ---
Loop Until 条件式
Sub DoLoopUntil_Basic()
    Dim i As Long 'カウンター
    i = 1

    Do
        Debug.Print i & "回目の処理"
        i = i + 1
    Loop Until i > 3

    Debug.Print "ループ終了(i は " & i & ")"
End Sub

こちらも i = 1 スタートなら出力は同じ4行になります。後判定なので、条件を満たしていても中身が必ず1回は実行される点が特徴。

実務では「『終了』と入力されるまで InputBox を出し続ける」処理がこの形の定番です。後ほど実務コードとして紹介しますね。

4パターン早見表と選択フローチャート

まずは4パターンを1枚の表で見比べてみましょう。

書き方判定の位置繰り返しが続く条件最低実行回数よく使う場面
Do While 条件 ... Loop前判定条件が True の間0回最終行まで読む(データ0件でも安全)
Do ... Loop While 条件後判定条件が True の間1回1回処理してから継続を判断したいとき
Do Until 条件 ... Loop前判定条件が False の間0回目標値に達するまで加算する
Do ... Loop Until 条件後判定条件が False の間1回入力を最低1回は必ず求める

表を眺めても迷うときは、次の3つの質問に順番に答えてみてください。1問ずつたどれば必ず1パターンに絞れます。

  • 質問1: 条件を満たしている「」回したいですか。それとも条件を満たす「まで」回したいですか。
    • 「間」→ While を使う
    • 「まで」→ Until を使う
  • 質問2: 条件の判定結果に関係なく、最低1回は必ず実行したいですか。
    • いいえ(0回でもよい)→ 判定をに置く(Do While 条件 / Do Until 条件
    • はい(必ず1回)→ 判定をに置く(Loop While 条件 / Loop Until 条件
  • 質問3: 途中で強制的に抜ける可能性がありますか。
    • はい → ループ内に If 条件 Then Exit Do を追加する(4パターン共通で使えます)

質問1と質問2の組み合わせで、4パターンのどれか1つに確定します。質問3は上乗せのオプションなので、どのパターンにも足せますよ。

同じ条件で4パターンを回すとこうなる

言葉の説明より、実行結果を見たほうが早いかもしれません。初期値は4パターンとも i = 10 で統一します。条件式も i <= 3(Until 側は i > 3)でそろえ、続けて動かしてみますね。

Sub CompareFourPatterns()
    Dim i As Long 'カウンター

    '--- 1: Do While ... Loop(前判定・満たす間) ---
    i = 10
    Debug.Print "【1】Do While ... Loop"
    Do While i <= 3
        Debug.Print "  実行: i = " & i
        i = i + 1
    Loop

    '--- 2: Do ... Loop While(後判定・満たす間) ---
    i = 10
    Debug.Print "【2】Do ... Loop While"
    Do
        Debug.Print "  実行: i = " & i
        i = i + 1
    Loop While i <= 3

    '--- 3: Do Until ... Loop(前判定・満たすまで) ---
    i = 10
    Debug.Print "【3】Do Until ... Loop"
    Do Until i > 3
        Debug.Print "  実行: i = " & i
        i = i + 1
    Loop

    '--- 4: Do ... Loop Until(後判定・満たすまで) ---
    i = 10
    Debug.Print "【4】Do ... Loop Until"
    Do
        Debug.Print "  実行: i = " & i
        i = i + 1
    Loop Until i > 3
End Sub

イミディエイトウィンドウの出力はこうなります。

【1】Do While ... Loop
【2】Do ... Loop While
  実行: i = 10
【3】Do Until ... Loop
【4】Do ... Loop Until
  実行: i = 10

結果を表に整理してみましょう。

パターン実行回数出力された行
Do While i <= 3 ... Loop0回(なし)
Do ... Loop While i <= 31回実行: i = 10
Do Until i > 3 ... Loop0回(なし)
Do ... Loop Until i > 31回実行: i = 10

前判定の2つは1行も出力しません。条件を満たしていないので、中身に入る前に終了するからです。対する後判定の2つは、それぞれ1回ずつ「実行: i = 10」を出力しました。

この非対称性が実務でどう効くかというと、データが0件のときに差が出ます。前判定なら空のシートに対して何もせず終了。後判定だと、データが無くても先頭の1行を処理してしまいます。空セルを計算に使ってエラーになりかねません。

TIP

迷ったら Do While ... Loop(前判定・While) を選んでおけば大きく外しません。最もよく使われる形ですし、条件を先にチェックするぶん安全側に倒れます。「必ず1回は実行したい」とはっきり言える場面だけ、後判定に切り替えましょう。

実務でよく使うDo Loopのコード4選

ここからは、業務でそのまま使えるコードを4本紹介します。どれも Cells(行, 列) でセルを指定しているのがポイント。行番号を変数で動かせるので、Do Loop とは相性が抜群なんです。

指定方法の違いが気になる方はRangeとCellsの使い分けものぞいてみてください。

各コードの冒頭には「なぜ For ではなく Do Loop なのか」を1行で添えました。ここが理解できると、自分のデータに当てはめるときの判断が速くなりますよ。

空白セルが出るまで下に読む(最終行が変動するデータ)

なぜ Do Loop か: データが何行あるかを、事前に数えなくていいから。

A列に商品名、B列に金額が並んだシートを想定します。空白セルに当たるまで下へ読み進め、B列の合計と件数を出すコードです。

A列(商品名)B列(金額)
1商品名金額
2りんご1200
3みかん800
4ぶどう2500
5(空白)(空白)
Sub SumUntilBlank()
    Dim ws As Worksheet '対象シート
    Dim i As Long '行カウンター
    Dim total As Double '合計金額

    Set ws = ActiveSheet
    i = 2 '1行目はヘッダーなので2行目から開始
    total = 0

    Do While ws.Cells(i, 1).Value <> ""
        total = total + ws.Cells(i, 2).Value
        i = i + 1
    Loop

    MsgBox "合計: " & Format(total, "#,##0") & "円" & vbCrLf & _
           "件数: " & (i - 2) & "件"
End Sub

上のサンプルデータで実行すると、A5が空白なのでループは3周で終わります。合計は 1200 + 800 + 2500 = 4500。ループを抜けた時点で i は5なので、件数は 5 - 2 = 3 件です。

表示されるメッセージはこうなります。

合計: 4,500円
件数: 3件

データが10行に増えても100行に増えても、コードは1文字も変えなくて大丈夫。ここが Do Loop のありがたさです。

なお、同じことは「先に最終行を確定させて For文で回す」やり方でも実現できます。詳しくはVBA最終行の取得方法をご覧ください。途中に空白行が混ざるデータでは、そちらのほうが安全なケースもありますよ。

目標金額に達するまで加算して行を特定する

なぜ Do Loop か: 何行目で目標に届くかは、実際に足してみるまで分からないから。

日別の売上を上から加算し、累計が100万円に達した行を特定するコードです。「〜まで」なので Until が自然に読めますね。

A列(日付)B列(売上)
1日付売上
24/1320000
34/2280000
44/3410000
54/4250000
Sub FindTargetRow()
    Dim ws As Worksheet '対象シート
    Dim i As Long '行カウンター
    Dim cumulative As Double '累計金額
    Dim target As Double '目標金額

    Set ws = ActiveSheet
    i = 2 '1行目はヘッダーなので2行目から開始
    cumulative = 0
    target = 1000000 '目標は100万円

    Do Until cumulative >= target
        If ws.Cells(i, 1).Value = "" Then
            MsgBox "データが尽きました。累計 " & _
                   Format(cumulative, "#,##0") & "円で目標未達です"
            Exit Sub
        End If
        cumulative = cumulative + ws.Cells(i, 2).Value
        i = i + 1
    Loop

    MsgBox "目標達成" & vbCrLf & _
           (i - 1) & "行目で累計 " & Format(cumulative, "#,##0") & "円"
End Sub

累計の動きを追ってみましょう。

  • 2行目を足して 320,000円 → まだ目標未達
  • 3行目を足して 600,000円 → まだ目標未達
  • 4行目を足して 1,010,000円 → ここで cumulative >= target が True になる

このときループを抜けた i は5です。実際に目標へ到達させたのは1つ前の4行目なので、i - 1 を表示しています。

目標達成
4行目で累計 1,010,000円

このコードの安全弁が If ws.Cells(i, 1).Value = "" Then の分岐。データが尽きても目標に届かない場合、この分岐がないと空白行を延々と読み続けてしまいます。

「終了」と入力されるまで受け付ける(後判定の出番)

なぜ Do Loop か: 何件入力されるかはユーザー次第で、事前に回数を決められないから。

入力ダイアログは「まず1回は必ず表示したい」もの。だから後判定の Loop Until がぴったりハマります。

Sub InputItemsLoop()
    Dim userInput As String '入力された文字列
    Dim itemList As String '登録した品名の一覧
    Dim itemCount As Long '登録件数

    itemList = ""
    itemCount = 0

    Do
        userInput = InputBox("品名を入力してください" & vbCrLf & _
                             "(「終了」と入力すると登録を終えます)", "品名登録")

        If userInput = "" Then Exit Do 'キャンセル・空入力なら中断
        If userInput <> "終了" Then
            itemList = itemList & userInput & vbCrLf
            itemCount = itemCount + 1
        End If
    Loop Until userInput = "終了"

    If itemCount > 0 Then
        MsgBox itemCount & "件を登録しました" & vbCrLf & itemList
    Else
        MsgBox "登録された品名はありません"
    End If
End Sub

「ノート」「ペン」「終了」の順に入力した場合を追ってみましょう。

  • 1周目: 「ノート」を登録(件数1)
  • 2周目: 「ペン」を登録(件数2)
  • 3周目: 「終了」が入力され、追加せずにループ終了

最終的に表示されるメッセージがこちらです。

2件を登録しました
ノート
ペン

見落としやすいのが If userInput = "" Then Exit Do の1行。InputBox のキャンセルボタンは空文字を返します。この分岐がないと「キャンセルしても終われない」状態になってしまうんです。

ユーザー入力を扱うループでは、キャンセルの脱出口を必ず用意すると覚えておきましょう。

条件に合うセルを上から順に検索する

なぜ Do Loop か: 「見つかったかどうか」を条件式にそのまま書けて、意図が読み取りやすいから。

A列を上から照合し、一致した行を選択するコードです。最終行は End(xlUp) で先に取得しておきます。

Sub SearchValueDownward()
    Dim ws As Worksheet '対象シート
    Dim i As Long '行カウンター
    Dim lastRow As Long '最終行
    Dim searchWord As String '検索する値
    Dim isFound As Boolean '見つかったかどうか

    Set ws = ActiveSheet
    searchWord = InputBox("検索する値を入力してください")
    If searchWord = "" Then Exit Sub 'キャンセル時は何もしない

    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
    i = 2
    isFound = False

    Do While i <= lastRow And Not isFound
        If ws.Cells(i, 1).Value = searchWord Then
            isFound = True
        Else
            i = i + 1
        End If
    Loop

    If isFound Then
        ws.Cells(i, 1).Select
        MsgBox searchWord & " は " & i & "行目に見つかりました"
    Else
        MsgBox searchWord & " は見つかりませんでした"
    End If
End Sub

A2からA5に「りんご」「みかん」「ぶどう」「もも」が入っているとしましょう。「ぶどう」で検索すると、2行目と3行目は不一致で i が進みます。

4行目で isFound が True に変わります。次の判定で Not isFound が False になり、ループ終了。

条件式の And Not isFound が効いていて、見つかった瞬間に止まる作りです。見つからなかった場合は ilastRow を超えて終了するので、無限ループにはなりません。

Find メソッドを使えば1行で書ける処理ではあります。それでも Do Loop で書く価値があるのは、照合しながら途中に別の処理を挟めるからなんですね。

おまけ:フォルダ内のファイルを順に処理する(Dir関数)

最後に、セル以外の定番パターンも1つ紹介しておきますね。

Dir 関数は、最初に Dir("パス*.xlsx") のようにパスを指定して呼び出します。2回目以降は引数なしの Dir() を呼ぶと、次のファイル名が返ってくる仕組み。該当ファイルが尽きると長さ0の文字列が返るので、それが終了の合図になります。

Sub ListFilesInFolder()
    Dim folderPath As String 'フォルダのパス
    Dim fileName As String 'ファイル名

    folderPath = "C:Sample"
    fileName = Dir(folderPath & "*.xlsx")

    Do While fileName <> ""
        Debug.Print fileName
        fileName = Dir() '引数なしで次のファイル名を取得
    Loop
End Sub

何件あるか分からないファイル群を順に処理する。まさに Do Loop の独壇場です。

無限ループを止める・防ぐ・抜ける

Do Loop で唯一こわいのが無限ループです。条件が永遠に True のままだと、Excel が応答しなくなってしまいます。

このセクションは3段構えでお届けします。

  • まず「いま固まっている人」向けの止め方
  • 次に途中脱出の Exit Do
  • 最後に、そもそも起こさないための書き方

いま固まっている人へ:Esc → Ctrl + Break → 強制終了

急いでいる方のために、対処を段階表にまとめました。上から順に試してみてください。

段階症状やること補足
1画面はまだ動く。ステータスバーに変化があるEsc キーを押すExcel 上での中断キー。まずはこれ
2画面が白い。タイトルバーに「応答なし」Ctrl + Break を押すWindows 全般で有効な中断キー
3どのキーも反応しないタスクマネージャーから Excel を終了未保存の変更は失われます

Mac をお使いの場合は Command + ピリオドが中断キーになります。

段階1と2で中断できると、ダイアログが表示されます。ボタンは「デバッグ」「続行」「終了」の3つ。ここで 「デバッグ」を選ぶのが正解です。

「デバッグ」を押すと、いま実行中の行が黄色くハイライトされます。そこが無限ループの現場。ハイライトされた DoLoop の間を見て、次の2点を確認してみてください。

  • カウンター変数を進める行(i = i + 1 など)が抜けていないか
  • 条件式が、いつか必ず False(Until なら True)になる作りか

VBE 側から止めることもできます。VBE のメニューで「実行」→「中断」を選ぶか、同じく Ctrl + Break を押す方法です。

中断したあとは F8 キーで1行ずつ実行し、変数の値がどう変わるかを追えます。この1行ずつ実行する操作こそステップ実行。詳しい手順はVBAデバッグの方法で解説しています。

NOTE

ノートPCによっては Break キーが独立して用意されていません。その場合の代替操作は機種によって異なるため、お使いの端末のサポート情報を確認してください。

強制終了の詳しい手順はExcelマクロが止まらない時の強制終了4ステップにまとめてあります。この記事では「Do Loop のどこを直すか」に絞ってお伝えしますね。

Exit Doで途中脱出する(If との組み合わせ・ネスト時の挙動)

Exit Do は、Do Loop を途中で抜けるためのステートメントです。ループ内の好きな場所に、いくつでも置けます。

単独で書くと必ず1周目で抜けてしまうので、実際には If とセットで使うのが基本形。

Do While 条件式
    If 異常を検知する条件 Then Exit Do  '安全弁
    '--- 通常の処理 ---
Loop

「想定外のデータが来たら抜ける」「上限回数に達したら抜ける」といった安全弁として働きます。条件分岐の書き方に不安があれば、VBA If文の使い方を先に押さえておくと理解が早いですよ。

ここで1つ、間違えやすい仕様があります。ループを入れ子にした場合の挙動です。入れ子(ネスト)とは、ループの中にさらに別のループを書く構造のこと。

Exit Do が抜けるのは、それが書かれている一番内側のループだけ。 外側のループは止まらず、そのまま続きます。

Sub ExitDoInNestedLoop()
    Dim outerNo As Long '外側のカウンター
    Dim innerNo As Long '内側のカウンター

    outerNo = 1
    Do While outerNo <= 2
        innerNo = 1
        Do While innerNo <= 3
            If innerNo = 2 Then Exit Do '内側だけを抜ける
            Debug.Print "outer=" & outerNo & " / inner=" & innerNo
            innerNo = innerNo + 1
        Loop
        Debug.Print "→ 外側は継続中(outer=" & outerNo & ")"
        outerNo = outerNo + 1
    Loop
End Sub

出力を見ると、外側が止まっていないことがはっきり分かります。

outer=1 / inner=1
→ 外側は継続中(outer=1)
outer=2 / inner=1
→ 外側は継続中(outer=2)

内側のループは innerNo が2になった時点で毎回抜けています。それでも外側は2周しました。Exit Do の直後にある「→ 外側は継続中」の行も、きちんと実行されていますね。

外側まで含めて一気に終わらせたいなら、Exit Sub を使います。こちらはプロシージャそのものを終了し、呼び出し元へ制御を返す命令。「ループを1つ抜けるのが Exit Do、処理全体を終えるのが Exit Sub」と整理しておきましょう。

そもそも起こさない書き方3つ(上限カウンター / DoEvents / 更新忘れ点検)

止め方を知っていても、止めずに済むのが一番ですよね。予防策を3つ紹介します。

1. 上限カウンターで安全弁をつける

ループが回った回数を数え、想定を超えたら強制的に抜ける方法です。条件式にバグがあっても、確実に終わりが来ます。

Sub SafeLoopWithLimit()
    Dim i As Long '行カウンター
    Dim guard As Long '安全弁カウンター
    Const MAX_LOOP As Long = 10000 '許容する最大周回数

    i = 2
    guard = 0

    Do While Cells(i, 1).Value <> ""
        guard = guard + 1
        If guard > MAX_LOOP Then
            MsgBox "ループが" & MAX_LOOP & "回を超えました。条件式を見直してください"
            Exit Do
        End If

        '--- ここに1行ごとの処理を書く ---
        i = i + 1
    Loop
End Sub

上限値は扱うデータ量に合わせて決めてください。数千行のデータなら10,000回もあれば十分です。書き慣れないうちは、この安全弁を必ず入れる習慣にしておくと安心。

2. DoEvents で中断を受け付けやすくする

DoEvents は、OS にイベント処理を渡す関数です。引数は不要で、ループ内に1行書くだけ。

Sub LoopWithDoEvents()
    Dim i As Long '行カウンター

    i = 2
    Do While Cells(i, 1).Value <> ""
        DoEvents 'OSにイベント処理を渡す
        Cells(i, 3).Value = Cells(i, 2).Value * 1.1
        i = i + 1
    Loop
End Sub

これを入れておくと、EscCtrl + Break が効きやすくなることがあります。長時間かかるループには入れておく価値がありますね。

ただし万能ではありません。OS 側の処理をはさむぶん、ループ全体の速度は落ちます。件数が多い処理では体感できるレベルで遅くなるため、必要な場面に絞って使いましょう。

3. カウンター更新忘れを自己点検する

無限ループの原因で圧倒的に多いのが、カウンター変数の更新忘れです。書き終えたら、次の3点をセルフチェックしてみてください。

  • カウンターを進める行(i = i + 1 など)が DoLoop の間にあるか
  • その行が If の中に入っていないか
  • 条件式が、いつか必ず終了側に転ぶ作りになっているか

2番目が特に落とし穴。次のコードは一見それらしく見えますが、確実に無限ループします。

' NG例: カウンターの更新が If の中に入っている
Sub BadLoop_CounterInsideIf()
    Dim i As Long '行カウンター
    i = 2

    Do While Cells(i, 1).Value <> ""
        If Cells(i, 2).Value > 0 Then
            i = i + 1  'B列が0以下の行に当たると、ここを通らない
        End If
    Loop
End Sub

B列に0や空白の行が1つでもあると、その行で i が止まったまま同じセルを見続けます。正しくは、次のようにカウンターを If の外へ出しましょう。

' OK例: スキップする行でもカウンターは必ず進む
Sub GoodLoop_CounterOutsideIf()
    Dim i As Long '行カウンター
    i = 2

    Do While Cells(i, 1).Value <> ""
        If Cells(i, 2).Value > 0 Then
            '--- 条件を満たす行だけの処理 ---
        End If
        i = i + 1  'どの行でも必ず進む
    Loop
End Sub

カウンターの更新は If の外。この一点を守るだけで、無限ループの大半は防げます。

よくある質問

質問に入る前に、Do Loop 特有のエラーを早見表にまとめておきます。エラーメッセージから逆引きしてみてください。

エラー表示出るタイミング原因直し方
コンパイルエラー: 「Loop がありません」実行前Do に対応する Loop が欠けているDoLoop の対応をインデントで確認する
コンパイルエラー: 「Do がありません」実行前Loop に対応する Do が欠けている余分な Loop を削るか、Do を補う
実行時エラー '6': オーバーフロー実行中カウンターが Integer の上限32,767を超えたDim i As Long に変更する
実行時エラー '91': オブジェクト変数または With ブロック変数が設定されていません実行中Set していないオブジェクト変数を参照したループ前に Set ws = ActiveSheet などで初期化する
応答なし(フリーズ)実行中条件が永遠に True/カウンターの更新忘れEscCtrl + Break で中断して原因行を確認

ここに載っていないエラー番号に遭遇したら、VBAマクロのエラー解決ガイドで番号から探せます。ブックマークしておくと便利ですよ。

While と Until はどちらを使えばいいですか?

動作は同じにできるので、条件式が自然に読めるほうを選んで大丈夫です。

Do While i <= 3Do Until i > 3 は、まったく同じ回数だけ回ります。While 側は条件を満たす「間」、Until 側は条件を満たす「まで」。同じ処理を書くなら、条件式を否定形にひっくり返すだけです。

読みやすさで選ぶなら、こんな基準が使えます。

  • 「〜でない間は続ける」と言いたい → While(例: Do While Cells(i, 1).Value <> ""
  • 「〜になったら終わり」と言いたい → Until(例: Do Until Cells(i, 1).Value = ""

日本語で読み上げてしっくりくるほうが、あとから読み返したときのバグも減ります。チームで共有するマクロならなおさら。

Do Loop の途中で次の周回に飛ばせますか?(VBAにContinueはない)

VBA には他言語の Continue にあたるステートメントがありません。 用意されているのは Exit DoExit For など、ループそのものを抜ける命令だけです。

代わりに使うのが If によるスキップ。「処理したい条件」で本体を囲み、カウンターの更新はその外に置きます。

Sub SkipBlankRows()
    Dim i As Long '行カウンター

    i = 2
    Do While Cells(i, 1).Value <> ""
        If Cells(i, 2).Value <> "" Then
            '--- B列に値がある行だけ処理する ---
            Cells(i, 3).Value = Cells(i, 2).Value * 1.1
        End If
        i = i + 1 'スキップした行でも必ず進める
    Loop
End Sub

GoTo とラベルでスキップを実装する方法もあります。ただし処理の流れが追いづらくなるため、まずは上の If で囲む書き方をおすすめします。

Do Loop は For~Next より遅いですか?

ループの種類による速度差を気にする必要は、ほとんどありません。

公式に「Do Loop のほうが遅い」といった規定はありません。実務で体感できるほどの差も出にくいものです。速度に効いてくるのは、ループの形式ではなくループの中で何をしているか

処理を速くしたいなら、次のような点を見直すほうが効果的ですよ。

  • 1周ごとにセルへ読み書きする回数を減らす(配列にまとめて処理する)
  • 画面の再描画を止める(Application.ScreenUpdating = False
  • 不要な DoEvents を外す

ループ形式の選択は、速度ではなく書きやすさと安全性で決めてくださいね。

Do Loop は入れ子(ネスト)にできますか?

できます。 行と列の両方向に走査したいときの定番パターンです。

Sub NestedDoLoop()
    Dim r As Long '行カウンター
    Dim c As Long '列カウンター

    r = 2
    Do While Cells(r, 1).Value <> ""
        c = 2 '外側の1周ごとに必ず初期化する
        Do While Cells(1, c).Value <> ""
            Cells(r, c).Value = Cells(r, c).Value * 1.1
            c = c + 1
        Loop
        r = r + 1
    Loop
End Sub

ネストで必ず守るべきルールは2つあります。まず、内側と外側で別々のカウンター変数を使うこと。同じ変数を使い回すと、互いの周回数が壊れてしまいます。

もう1つが、内側のカウンターを外側の1周ごとに初期化すること。上のコードで c = 2 を内側ループの直前に書いているのはこのためです。初期化を忘れると、2周目以降は c が最終列を超えたままなので、内側が1回も回りません。

なお、内側のループに書いた Exit Do が抜けるのは内側だけ。外側は継続します。この挙動は前のセクションで、実際の出力とともに確認しましたね。

「Loopがありません」「オーバーフロー」が出るのはなぜ?

「Loop がありません」は、Do に対応する Loop が見つからないときのコンパイルエラーです。 コンパイルエラーとは、実行する前の構文チェックで見つかる文法上の誤りのこと。

ネストが深いと発生しやすいエラーです。DoLoop を同じインデント位置に揃えて書く習慣をつけましょう。逆に Loop が多すぎる場合は「Do がありません」と表示されます。

オーバーフロー(実行時エラー '6')は、変数に入りきらない値を入れようとしたときのエラーです。 Integer 型が扱えるのは -32,768 〜 32,767 の範囲だけ。

Excel の行数は最大1,048,576行あります。行カウンターに Integer を使うと、途中で必ず破綻してしまうんですね。

対処はシンプルで、Long 型に変更するだけ。Long なら -2,147,483,648 〜 2,147,483,647(約21億)まで扱えます。ループのカウンターは常に Long と決めてしまうのが安全です。

もう1つ、見落としやすいパターンがあります。計算の途中結果がオーバーフローするケースです。

Dim total As Long

total = 2000 * 365       'オーバーフローになる
total = CLng(2000) * 365 'OK(先に Long へ変換する)

原因は右辺にあります。代入先が Long でも、整数どうしの計算は一時的に Integer として扱われるんです。CLng で明示的に変換すれば回避できますよ。

まとめ

Do Loop は「回数ではなく条件で終わりを決める」ためのステートメントです。最後に要点を振り返っておきましょう。

  • 使い分け: 回数が決まっていない繰り返しは Do Loop。決まっているなら For~Next、集合をまるごとなら For Each
  • While と Until: While は条件を満たす「間」、Until は条件を満たす「まで」。条件式を否定形にすれば置き換え可能
  • 前判定と後判定: 前判定(Do While / Do Until)は0回実行もあり得る。後判定(Loop While / Loop Until)は必ず1回実行される
  • 迷ったら: Do While ... Loop を選べば大きく外さない
  • 無限ループ対策: 止めるのは EscCtrl + Break。防ぐのは上限カウンター・Exit Do・カウンター更新の点検
  • カウンターは Long: Integer は32,767でオーバーフローする

一番のつまずきポイントは、やはりカウンターの更新忘れです。DoLoop の間を見て「この変数、ちゃんと進んでる?」と自問する癖をつけましょう。それだけで無限ループはぐっと減らせます。

条件付きの繰り返しが書けるようになると、VBA でできることの幅が一気に広がりますよ。まずは4パターンの最小コードを VBE に貼り付けて、イミディエイトウィンドウの出力を眺めてみてください。動きが目で見えると、前判定と後判定の違いもすっと腹落ちするはずです。

関連記事

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