【Excel VBA入門 第18回】Err.Raiseでエラーを伝える!後始末を忘れないマクロの作り方

スポンサーリンク
Excel VBA

イントロダクション

前回(第17回)は、On Error Resume Next でエラーを無視する方法と、Resume でエラーの行をやり直す方法を学んだ。

今回は、エラー処理編の応用として、次の2つを取り上げる。

  • Err.Raise:Function の中で見つけた問題を、呼び出したSub側に伝える方法
  • 後始末:エラーが起きても、ScreenUpdating などの設定を元に戻す書き方

Functionの中でエラーを発生させる

ここで、第16回で作った 割り算 Function を思い出そう。
0で割ると、VBAが用意している「実行時エラー11」が自動的に発生した。

では、「マイナスの数を渡したら不正な値としてエラーにしたい」といった、VBA標準にはないルールを作りたい場合はどうすればよいか。
そこで使うのが Err.Raise である。これは、任意のタイミングで自分からエラーを発生させる命令である。

Function 平方根を求める(値 As Double) As Double
    If 値 < 0 Then
        Err.Raise Number:=vbObjectError + 1, _
                  Description:="マイナスの値には対応していません。"
    End If

    平方根を求める = Sqr(値)
End Function
  • Number:エラー番号。自作のエラーには vbObjectError + 任意の数値 を使うのが決まりである。これにより、VBAが元々使っている番号と重ならなくなる
  • Description:エラーの内容を表す文字列。Err.Description として、呼び出し元に伝わる

呼び出し元でエラーを受け取る

Err.Raise で発生させたエラーも、第16回で学んだ On Error GoTo で受け取れる。VBAが自動的に起こすエラーと、扱い方は同じである。

Sub 平方根の計算()
    Dim 入力値 As Double
    Dim 結果 As Double

    On Error GoTo エラー処理

    入力値 = CDbl(InputBox("平方根を求める数を入力してください"))
    結果 = 平方根を求める(入力値)
    MsgBox "平方根は " & 結果 & " です。"
    Exit Sub

エラー処理:
    MsgBox "計算できませんでした。" & vbCrLf & Err.Description, vbExclamation
End Sub

-4 を入力すると、Function 内の Err.Raise で発生したメッセージがそのまま表示される。
13(型が一致しません)のような、VBAが用意した番号と自作のエラーを、同じ仕組みで扱えることが分かるだろう。

(注)-4は、平方根の計算Subを実行したときに表示されるInputBox(「平方根を求める数を入力してください」というボックス)に入力しよう。

なぜFunctionの中でMsgBoxを出さないのか

「エラーが起きたら Err.Raise を使わず、Function の中で直接 MsgBox を表示すればよいのでは」と思うかもしれない。しかし、これには問題がある。

Function は、いろいろな場所から呼び出されて使い回されるものである。Function の中に MsgBox を書いてしまうと、呼び出し元の事情を無視して、常に同じダイアログが表示されてしまう。たとえば、ループの中で100回呼び出されたら、100回ダイアログが出て止まってしまう。

Err.Raise でエラーを「伝える」だけにしておけば、それをどう扱うか(メッセージを出す、ログに記録する、既定値を使うなど)を呼び出し元が決められる。
これは第12回で学んだ「Functionは値を返すことに専念する」という考え方の延長線上にある。

後始末を忘れないマクロ

エラー処理でもう一つ大事なのが「後始末」である。処理の途中で Application.ScreenUpdating = False(画面の更新を止める)のような設定をしていた場合、エラーで処理が中断すると、その設定を元に戻す行が実行されないままになってしまう。

わかりやすい例で見てみよう。まず、後始末をしていない、問題のあるコードである。

Sub 後始末なしの例()
    Application.ScreenUpdating = False

    Dim i As Long
    For i = 1 To 10
        Cells(i, 1).Value = 100 / (i - 5)  ' i=5のとき0で割ってエラー
    Next i

    Application.ScreenUpdating = True
End Sub

i = 5 のときにエラーが起き、マクロは途中で止まる。すると、最後の Application.ScreenUpdating = True が実行されないまま終わってしまう。
画面上は更新が止まったままになり、Excelの操作がおかしく見えてしまう。

後始末なしの例を実行すると、i = 5のときに100 / (i - 5)で0除算になるので、「実行時エラー ’11’: 0 で除算しました。」というエラーダイアログが出る。

(注)このエラーで処理が止まると、Application.ScreenUpdating = Falseが実行されたままになり、元に戻されない。そのままだと、この後の操作でExcelの画面更新が止まったように見えることがある。

確認のため、VBEでCtrl + Gを押してイミディエイトウィンドウを開き、

Application.ScreenUpdating = True

と入力してEnterを押し、画面更新を手動で戻しておこう(この流れが、次の後始末ありの例で解決する問題そのものである)。

さて、これを、エラー処理を使って直してみよう。

Sub 後始末ありの例()
    On Error GoTo エラー処理

    Application.ScreenUpdating = False

    Dim i As Long
    For i = 1 To 10
        Cells(i, 1).Value = 100 / (i - 5)
    Next i

後始末:
    Application.ScreenUpdating = True
    Exit Sub

エラー処理:
    MsgBox "処理中にエラーが発生しました。" & vbCrLf & Err.Description, vbExclamation
    Resume 後始末
End Sub

ここでのポイントは、後始末: というラベルを、正常終了時とエラー発生時の両方から通る場所に置いていることである。

  • 正常に終われば、そのまま 後始末: に流れ込み、ScreenUpdating を元に戻す
  • エラーが起きれば エラー処理: に飛び、メッセージを表示した後、Resume 後始末 で 後始末: へジャンプする

前回学んだ Resume ラベル名 が、ここで活きてくる。「後始末は1か所にまとめ、正常時もエラー時もそこを通す」という形は、覚えておくと役に立つ。

まとめ

今回は、エラーの応用的な扱い方を学んだ。

  • Err.Raise で、自分の決めたルールに沿ったエラーを発生させられる
  • 番号には vbObjectError + 任意の数値 を使う
  • Function の中で MsgBox を出さず、Err.Raise で呼び出し元に判断を委ねる
  • 後始末(設定を元に戻す処理)は1か所にまとめ、正常時もエラー時も Resume ラベル名 で通す

次回(第19回)は、エラー処理編の最終回として、「そもそもエラーを起こさない」ための事前チェックの考え方を取り上げる。IsNumeric や、第17回で扱ったシート存在確認を事前チェックの形に書き直しながら、シリーズ全体を振り返っていこう。

コメント

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