エラーに負けない


マクロ開発者にとってエラーは嫌なものです。できればエラーメッセージは見たくありませんし、対処のしかたに慣れていないと、エラーの原因を解明するために思わぬ時間が必要だったりもします。もちろんエラーは発生させないのが一番ですが、そうも言っていられません。ここでは、エラーを回避するテクニックをご紹介します。

エラーを無視する

たとえば次のコードは、ワークシート名を取得して表示します。

Sub Sample1()
    Dim i As Long, buf As String
    For i = 1 To 4
        buf = buf & Worksheets(i).Name & vbCrLf
    Next i
    MsgBox buf
End Sub

しかし、For Nextの繰り返しを4回実行しているため、ワークシートが3枚以下だとエラーになります。

For Nextのループ中でエラーが発生してマクロはストップしますので、For Nextの次に書いたMsgBoxは実行されません。エラーが発生してもマクロを停止せず、マクロが発生した次行から処理を継続させるには、マクロが発生するより前で、On Errorステートメントを実行します。この場合「エラーを無視」したいのですから、On Error Resume NextのようにResume Nextをつけます。これはもう、英単語の意味とか理屈ではなく、暗記してください。

Sub Sample1()
    Dim i As Long, buf As String
    On Error Resume Next
    For i = 1 To 4
        buf = buf & Worksheets(i).Name & vbCrLf
    Next i
    MsgBox buf
End Sub

エラーが発生する前にOn Error Resume Nextが実行されていると、エラーが発生したとき、そのエラーが発生した次の命令から処理が継続されます。たとえば次のコードは、Sheet1~Sheet3までのシート名を取得して表示しますが、もしSheet2が存在しない場合は Worksheets("Sheet" & 2).Name がエラーになります。しかし、For Nextの繰り返しは継続されるので、Sheet3は取得されます。

Sub Sample2()
    Dim i As Long, buf As String
    On Error Resume Next
    For i = 1 To 3
        buf = buf & Worksheets("Sheet" & i).Name & vbCrLf
    Next i
    MsgBox buf
End Sub

エラーが起きたらジャンプする

エラーが発生したとき、通常の流れとは別の処理をさせたいことがあります。たとえば、あるファイルを開いて、そこに書かれているデータをセルに入力するようなケースでは、何らかの理由でファイルを開けなかったとき、強制的に処理を中断する必要があります。そんなときは、On Error Goto 《ラベル名》 を使います。《ラベル名》にはジャンプ先のラベルを指定します。次のコードは、C:\Sample.dat を開き先頭のデータをセルA1に読み込みます。何らかの理由で C:\Sample.dat を開けなかったときはエラーが発生しますので、ラベル myError にジャンプして処理を分岐しています。

Sub Sample3()
    Dim buf As String
    On Error GoTo myError
    Open "C:\Sample.dat" For Input As #1   ''ここでエラーが発生すると
        Line Input #1, buf
        Range("A1") = buf
    Close #1
    Exit Sub
myError:                                        ''この行にジャンプします
    MsgBox "ファイルを開けません", vbExclamation
End Sub

ラベルは、行頭から任意の文字列(《ラベル名》)を書き、行末にコロン「:」をつけます。

注意しなければならないのは、エラーが発生しなかった場合を想定することです。もしエラーが発生せず、コードの上から順に処理が行われたなら、最後の MsgBox が毎回実行されてしまいます。そこで、エラーが発生しない(ラベルにジャンプしない)ときは、ラベルの手前に Exit Sub と「Subプロシージャを終了させる」コマンドを忘れずに記述します。

※エラーが発生した場合
Sub Sample3()
    Dim buf As String
    On Error GoTo myError
    Open "C:\Sample.dat" For Input As #1 ''ここでエラーが発生してmyErrorにジャンプする
        Line Input #1, buf
        Range("A1") = buf
    Close #1
    Exit Sub
myError:
    MsgBox "ファイルを開けません", vbExclamation
End Sub		''ここで終了する
※エラーが発生しない場合
Sub Sample3()
    Dim buf As String
    On Error GoTo myError
    Open "C:\Sample.dat" For Input As #1
        Line Input #1, buf
        Range("A1") = buf
    Close #1
    Exit Sub		''ここで終了する
myError:
    MsgBox "ファイルを開けません", vbExclamation
End Sub

もうひとつ重要なポイントがあります。たとえば次のようなケースを考えてみましょう。Sample3で、Sample.datファイルから読み込んだデータをセルA1に入力した後、続けてSheet2の名前を"合計"に変更します。

Sub Sample4()
    Dim buf As String
    On Error GoTo myError
    Open "C:\Sample.dat" For Input As #1
        Line Input #1, buf
        Range("A1") = buf
    Close #1
    Worksheets("Sheet2").Name = "合計"      ''追加した処理(ここでエラーになる)
    Exit Sub
myError:
    MsgBox "ファイルを開けません", vbExclamation
End Sub

ここでは、C:\Sample.dat は無事に開けた(エラーにならなかった)とします。しかし、このブックにはSheet2が存在しなかったとしたら。Worksheets("Sheet2").Name がエラーになります。で、実行してみると・・・

そう、On Error GoTo myError の後プロシージャが終了するまでは、どんなエラーが発生しても全部 myError にジャンプしてしまうんです。これでは使い勝手がよくありません。そこで「エラーが発生したらジャンプする」に対して「そのジャンプ命令の取り消し」をすることができます。それには On Error GoTo 0 と書きます。最後のは半角のゼロです。On Error Goto 《ラベル名》でエラー発生時に別処理へジャンプさせるときは、予期せぬ場所(エラー)からジャンプしないように注意してください。

もちろん、On Error Goto 0でエラーに対する処理を終了したのですから、Sheet2が存在しないと「Sheet2が存在しない」という別のエラーが発生します。それはそれで、何らかのエラー対策をしなければなりませんね。

エラーの意味を調べる

エラーが発生すると、どんなエラーが発生したかの情報が Errオブジェクトに格納されます。これは、On Error Resume Nextで「エラーが起きても停止しない」にしていても同様です。On Error Resume Nextは「エラー(で停止すること)を無視する」だけで「エラーを発生させない」命令ではありません。On Error Resume Nextを実行していても、エラーが発生したときには、どんなエラーが発生したかの情報がErrオブジェクトに格納されます。

エラーが発生したときは、Errオブジェクトのプロパティを調べることでどんなエラーが発生したかを調べることができます。Errオブジェクトでよく使われるプロパティとメソッドは次の通りです。これくらいを覚えておけばいいでしょう。

プロパティまたはメソッド 働き
Number プロパティ エラーの種類を識別する番号が格納されます
Description プロパティ エラーに関する簡単な説明が格納されます
Clear メソッド エラーの情報を初期化します

Numberプロパティには、エラーごとに異なる番号が格納されます。値を設定することもできますが、一般的な使い方では設定しないでしょう。上記Sample3プロシージャを例にすれば、ファイルを開けなかった(Openメソッドが失敗した)ときのNumberプロパティは53で、Worksheets("Sheet2")が存在しなかったときのエラー番号は9です。実際に確認してみましょう。

Sub Sample3()
    Dim buf As String
    On Error GoTo myError
    Open "C:\Sample.dat" For Input As #1        ''ここでエラーが発生すると
        Line Input #1, buf
        Range("A1") = buf
    Close #1
    Exit Sub
myError:                                        ''この行にジャンプします
    MsgBox "エラー番号:" & Err.Number & vbCrLf & _
           "エラーの種類:" & Err.Description, vbExclamation
End Sub

エラーが発生したときは、ErrオブジェクトのNumberプロパティにエラーの番号が格納されます。エラーが発生するまで、Numbersプロパティは「0」が入っています。つまり、ErrオブジェクトのNumberプロパティが0でないということは、「エラーが発生した」と判断できるわけです

Sub Sample4()
    Dim buf As String
    On Error Resume Next
    Open "C:\Sample.dat" For Input As #1
        Line Input #1, buf
        Range("A1") = buf
    Close #1
    If Err.Number <> 0 Then
        MsgBox "エラーが発生しました" & vbCrLf & _
               Err.Description, vbExclamation
    End If
    Worksheets("Sheet2").Name = "合計"
End Sub

注意しなければならないのは、発生したエラーに関する情報は、プロシージャが終了するまでErrオブジェクトに格納され続けるということです。次のケースを考えてみましょう。

  1. Sample.datを開く
  2. Sheet2の名前を設定する

という2つの処理に対して、それぞれ「エラーが発生したかどうか」をErrオブジェクトのNumberプロパティで判定します。このとき、

  1. Sample.datを開く → エラー
  2. Sheet2の名前を設定する → 正常

だった場合、どうなるでしょう。

Sub Sample4()
    Dim buf As String
    On Error Resume Next
    Open "C:\Sample.dat" For Input As #1        ''ここでエラー
        Line Input #1, buf
        Range("A1") = buf
    Close #1
    If Err.Number <> 0 Then
        MsgBox "エラーが発生しました" & vbCrLf & _
               Err.Description, vbExclamation
    End If
    Worksheets("Sheet2").Name = "合計"          ''ここは正常
    If Err.Number <> 0 Then
        MsgBox "シート名を変更できませんでした" & vbCrLf & _
               Err.Description, vbExclamation
    End If
End Sub

Sheet2の名前変更は正常に完了したにもかかわらず、二度目の「Err.Number <> 0」で"エラーが発生した"と判断されてしまいました。Sample.datが開けたかどうかの「最初のエラー判定」が終わった後は、もうそのエラー情報が不要なのですから、エラー情報をクリアしなければなりません。

Sub Sample4()
    Dim buf As String
    On Error Resume Next
    Open "C:\Sample.dat" For Input As #1        ''ここでエラー
        Line Input #1, buf
        Range("A1") = buf
    Close #1
    If Err.Number <> 0 Then
        MsgBox "エラーが発生しました" & vbCrLf & _
               Err.Description, vbExclamation
    End If
    Err.Clear
    Worksheets("Sheet2").Name = "合計"          ''ここは正常
    If Err.Number <> 0 Then
        MsgBox "シート名を変更できませんでした" & vbCrLf & _
               Err.Description, vbExclamation
    End If
End Sub

エラーの種類を自動判定する

Errオブジェクトと、先に解説したOn Error Gotoを併用すると、エラーの種類に応じて適切な対応を準備できます。上記Sample4を例に解説しましょう。ここでは

  1. Sample.datを開く
  2. Sheet2の名前を設定する

の、どちらでエラーが発生するか予測できません。そこで、何らかのエラーが発生したら特定のラベルにジャンプして、そこで"どんなエラーが発生したか"を判定します。

Sub Sample4()
    Dim buf As String
    On Error GoTo myError
    Open "C:\Sample.dat" For Input As #1
        Line Input #1, buf
        Range("A1") = buf
    Close #1
    Worksheets("Sheet2").Name = "合計"
    Exit Sub
myError:
    Select Case Err.Number
    Case 9
        MsgBox "シート名を変更できませんでした" & vbCrLf & Err.Description, vbExclamation
    Case 53
        MsgBox "ファイルを開けませんでした" & vbCrLf & Err.Description, vbExclamation
    Case Else
        MsgBox "予期せぬエラーが発生しました", vbExclamation
    End Select
End Sub

これで、エラーが発生すると、

または

または

が表示されます。ポイントは最後の「Case Else節」です。これを加えることで"想定外のエラー"が発生した場合でも、とりあえずはマクロが停止することはありません。