和風スパゲティのレシピ

日本語でコーディングするExcelVBA

プロシージャのCall元/先でのOnError挙動まとめ

プロシージャをCallする際、親子どちらでOnErrorを設定したかによって、
どのように処理内容が変化するかをまとめました。

エラー処理を行う際の参考にして下さい。


※ 便宜上Callと書いていますが、Functionの戻り値を変数に入れるなど、
 プロシージャを呼び出す処理すべて同じ挙動となります。

子だけOn Error Resume Next

OnErrorとCallの関係を知る上で、最も単純かつ重要なコードがこちらです。

Sub 親プロシージャ()
    Call 子プロシージャ
    Cells(0, 0).Select ' エラー発生
End Sub

Sub 子プロシージャ()
    On Error Resume Next
    Cells(0, 0).Select ' エラー発生せず
End Sub

まずはこのコードをしっかり読んで、「OnError○○はプロシージャごとの設定」ということを理解して読み進めてください。


On Error Resume Nextを実行した後ずっとエラーがスキップされるわけではなく
On Error Resume Nextを実行したSubがEndした時点でスキップされなくなる

というのが重要なポイントです。

親だけOn Error Resume Next

続いて親だけにエラー処理を書いた場合です。

Sub 親プロシージャ()
    On Error Resume Next
    Call 子プロシージャ
    Cells(0, 0).Select ' エラー発生せず
End Sub

Sub 子プロシージャ()
    Cells(0, 0).Select ' エラー発生せず
    MsgBox "このコードは実行されない"
End Sub

親でOn Error Resume Nextを指定した場合は、
子でエラーが起きた時もエラー自体はスキップされます。

ただし、このMsgBoxが表示されないことからわかる通り、
子でエラーが発生した時点でCall元の親に戻るという挙動に注意してください。


「エラーが起きたら次の行に進む」といった場合のこの「次」は、
On Error Resume Nextを実行した親にとっての次ということですね。

エラーが起きたのでCallの次の行に飛んだと理解するとわかりやすいですね。

親子どちらにもOn Error Resume Next

Sub 親プロシージャ()
    On Error Resume Next
    Call 子プロシージャ
    Cells(0, 0).Select ' エラー発生せず
End Sub

Sub 子プロシージャ()
    On Error Resume Next
    Cells(0, 0).Select ' エラー発生せず
    MsgBox "このコードは実行される"
End Sub

親子どちらにもOn Error Resume Nextを書いた場合は、
素直にどちらのエラーもスキップされます。

親子どちらにもOnErrorGoTo

続いてより詳しく調べるためにGoToでジャンプしてみます。

Sub 親プロシージャ()
    On Error GoTo エラー時
    Call 子プロシージャ
    Exit Sub
    
エラー時:
    MsgBox "このコードは実行されない"
End Sub

Sub 子プロシージャ()
    On Error GoTo エラー時
    Cells(0, 0).Select
    Exit Sub
    
エラー時:
    MsgBox "このコードは実行される"
End Sub

このコードを実行してみるとわかりますが、
親側のOnErrorは「エラー時」にジャンプしていません。

つまり親側ではエラーが起きたという扱いにはなっておらず、
「子のOnErrorで処理されたエラーは親にとってはエラーでない」
と解釈することができるということです。


子で想定していたことなので想定外(例外)ではない
と捉えるとわかりやすいでしょうか。


そしてもう一つ、親側でOnErrorGoToを書いていますが、
子で発生したエラーはちゃんと子のOnErrorGoToで処理されます。

親のGotoに従って子プロシージャから離脱してしまうことはありません。


このように、親子どちらにもOnError処理を書いた場合は、
双方のOnErrorは独立してちゃんと動いてくれます。

ありがたい仕様ですね。

親のOnErrorGoTo vs 子のOn Error Resume Next

Sub 親プロシージャ()
    On Error GoTo エラー時
    Call 子プロシージャ
    
    MsgBox "このコードは実行される"
    
    Cells(0, 0).Select
    Exit Sub
    
エラー時:
    MsgBox "このコードは実行される"
End Sub

Sub 子プロシージャ()
    On Error Resume Next
    Cells(0, 0).Select
End Sub

ここまでの仕様を理解していれば復習みたいなものですが、
子のOn Error Resume Nextによって親のOnErrorが切れたりはしません。

子プロシージャのCall(中でのエラースキップ)は問題なく行われ、
そのあとの親プロシージャのエラーはしっかりGoToでジャンプされます。

親のOn Error Resume Next vs 子のOnErrorGoTo

Sub 親プロシージャ()
    On Error Resume Next
    Call 子プロシージャ
    
End Sub

Sub 子プロシージャ()
    On Error GoTo エラー時
    Cells(0, 0).Select

    Exit Sub
    
エラー時:
    MsgBox "このコードは実行される"
    Stop
End Sub

こちらも同じく復習みたいなものですが、
親でOn Error Resume Nextとしていたとしても、
子でOn Error GoToを指定すればちゃんとジャンプ
します。


試しに「Stop」も書いてみましたが、こうすることにより、
いかに親がOn Error Resume Nextしようとも実行を止める
なんてエラー対応を書くことも可能です。

Errオブジェクトの中身は共有

さてここまで「OnErrorの設定はプロシージャごとのもの」と書いてきましたが、
Errオブジェクトの中身だけは例外になります。

Sub 親プロシージャ()
    On Error Resume Next
    Call 子プロシージャ
    
    If Err.Number > 0 Then
        MsgBox Err.Description
    End If
End Sub

Sub 子プロシージャ()
    On Error Resume Next
    Cells(0, 0).Select
End Sub

このコードを実行すると、ちゃんと

アプリケーション定義またはオブジェクト定義のエラーです。

というMsgBoxが表示されます。


子プロシージャで想定していないエラーだけをキャッチしたい
ような場合はErr.Numberによる判定方法は使えません。

素直にOnErrorGoToで分岐を書いてください。


エラー処理はプロシージャごとに独立と思っていると、

If Err.Number > 0 Then

この判定が独立でない罠にはまりますのでご注意ください。



以上でCallとOnErrorの親子関係による挙動の変化の解説を終わります。

  • 親のOnErrorは子のOnErrorに干渉しない
  • 子のOnErrorで捉えたエラーは親のOnErrorにはかからない
  • 子で捉えなかったエラーを親のOnErrorが捕まえた場合は、子のそれ以降のコードは実行されない

この3つを押さえておけばいいんじゃないかと思います。

いずれも知っていればとても扱いやすい仕様ですので、
便利に活用していきましょう。