web-dev-qa-db-ja.com

VBAエラー処理に適したパターン

VBAのエラー処理に適したパターンは何ですか?

特に、この状況ではどうすればよいですか:

... some code ...
... some code where an error might occur ...
... some code ...
... some other code where a different error might occur ...
... some other code ...
... some code that must always be run (like a finally block) ...

両方のエラーを処理し、エラーが発生する可能性のあるコードの後に​​実行を再開します。また、最後に最後のコードを実行する必要があります-どんな例外が以前にスローされても関係ありません。どうすればこの結果を達成できますか?

70
jwoolard

VBAでのエラー処理


  • On Error GotoErrorHandlerLabel
  • ResumeNext |ErrorHandlerLabel
  • On Error Goto 0(現在のエラーハンドラを無効にします)
  • Errオブジェクト

Errオブジェクトのプロパティは、通常、エラー処理ルーチンでゼロまたは長さゼロの文字列にリセットされますが、Err.Clearを使用して明示的に行うこともできます。

エラー処理ルーチンのエラーは終了します。

範囲513〜65535は、ユーザーエラーに使用できます。カスタムクラスエラーの場合、vbObjectErrorをエラー番号に追加します。 Err.Raise および エラー番号のリスト に関するMSドキュメントを参照してください。

derivedクラスの実装されていないインターフェイスメンバの場合、定数E_NOTIMPL = &H80004001を使用する必要があります。


Option Explicit

Sub HandleError()
  Dim a As Integer
  On Error GoTo errMyErrorHandler
    a = 7 / 0
  On Error GoTo 0

  Debug.Print "This line won't be executed."

DoCleanUp:
  a = 0
Exit Sub
errMyErrorHandler:
  MsgBox Err.Description, _
    vbExclamation + vbOKCancel, _
    "Error: " & CStr(Err.Number)
Resume DoCleanUp
End Sub

Sub RaiseAndHandleError()
  On Error GoTo errMyErrorHandler
    ' The range 513-65535 is available for user errors.
    ' For class errors, you add vbObjectError to the error number.
    Err.Raise vbObjectError + 513, "Module1::Test()", "My custom error."
  On Error GoTo 0

  Debug.Print "This line will be executed."

Exit Sub
errMyErrorHandler:
  MsgBox Err.Description, _
    vbExclamation + vbOKCancel, _
    "Error: " & CStr(Err.Number)
  Err.Clear
Resume Next
End Sub

Sub FailInErrorHandler()
  Dim a As Integer
  On Error GoTo errMyErrorHandler
    a = 7 / 0
  On Error GoTo 0

  Debug.Print "This line won't be executed."

DoCleanUp:
  a = 0
Exit Sub
errMyErrorHandler:
  a = 7 / 0 ' <== Terminating error!
  MsgBox Err.Description, _
    vbExclamation + vbOKCancel, _
    "Error: " & CStr(Err.Number)
Resume DoCleanUp
End Sub

Sub DontDoThis()

  ' Any error will go unnoticed!
  On Error Resume Next
  ' Some complex code that fails here.
End Sub

Sub DoThisIfYouMust()

  On Error Resume Next
  ' Some code that can fail but you don't care.
  On Error GoTo 0

  ' More code here
End Sub
97
guillermooo

私も追加します:

  • グローバルErrオブジェクトは、例外オブジェクトに最も近いオブジェクトです。
  • Err.Raiseで効果的に「例外をスロー」できます

そしてただの楽しみのために:

  • On Error Resume Nextは悪魔の化身であり、エラーを静かに隠すため、避けるべきです
35
Joel Goodwin

だから、あなたはこのようなことをすることができます

Function Errorthingy(pParam)
On Error GoTo HandleErr

 ' your code here

    ExitHere:
    ' your finally code
    Exit Function

    HandleErr:
        Select Case Err.Number
        ' different error handling here'
        Case Else
            MsgBox "Error " & Err.Number & ": " & Err.Description, vbCritical, "ErrorThingy"
        End Select


   Resume ExitHere

End Function

カスタム例外を焼きたい場合。 (例:ビジネスルールに違反するもの)上記の例を使用しますが、必要に応じてgotoを使用してメソッドのフローを変更します。

17
Johnno Nolan

これが私の標準的な実装です。ラベルが自己記述的であることを好みます。

Public Sub DoSomething()

    On Error GoTo Catch ' Try
    ' normal code here

    Exit Sub
Catch:

    'error code: you can get the specific error by checking Err.Number

End Sub

または、Finallyブロックで:

Public Sub DoSomething()

    On Error GoTo Catch ' Try

    ' normal code here

    GoTo Finally
Catch:

    'error code

Finally:

    'cleanup code

End Sub
11
LimaNightHawk

Professional Excel Development は非常に優れています エラー処理スキーム 。 VBAで時間を過ごすなら、この本を入手する価値があるでしょう。 VBAが不足している領域がいくつかあり、この本にはそれらの領域を管理するための良い提案があります。

PEDでは、2つのエラー処理方法について説明しています。主なものは、すべてのエントリポイントプロシージャがサブプロシージャであり、他のすべてのプロシージャがブール値を返す関数であるシステムです。

エントリポイントプロシージャは、On Errorステートメントを使用して、設計どおりにエラーをキャプチャします。非エントリポイントプロシージャは、エラーがなければTrueを返し、エラーがあればFalseを返します。エントリポイント以外のプロシージャもOn Errorを使用します。

どちらのタイプの手順でも、中央のエラー処理手順を使用して、エラーの状態を維持し、エラーを記録します。

4
Dick Kusleika

以下のコードは、サブ/関数の出口点が1つだけであることを保証する代替手段を示しています。

sub something()
    on error goto errHandler

    ' start of code
    ....
    ....
    'end of code

    ' 1. not needed but signals to any other developer that looks at this
    ' code that you are skipping over the error handler...
    ' see point 1...
    err.clear

errHandler:
    if err.number <> 0 then
        ' error handling code
    end if
end sub
3
nickD

私は自分で開発したコードを使用していますが、それは私のコードにとってかなり良いものです。

関数またはサブの冒頭で、以下を定義します。

On error Goto ErrorCatcher:

そして、私は可能なエラーを処理します

ErrorCatcher:
Select Case Err.Number

Case 0 'exit the code when no error was raised
    On Error GoTo 0
    Exit Function
Case 1 'Error on definition of object
    'do stuff
Case... 'little description here
    'do stuff
Case Else
    Debug.Print "###ERROR"
    Debug.Print "   • Number  :", Err.Number
    Debug.Print "   • Descrip :", Err.Description
    Debug.Print "   • Source  :", Err.Source
    Debug.Print "   • HelpCont:", Err.HelpContext
    Debug.Print "   • LastDLL :", Err.LastDllError
    Stop
    Err.Clear
    Resume
End Select
3
Thiago Cardoso

また、議論に関連するのは、比較的未知のErl関数です。コードプロシージャ内に数値ラベルがある場合、たとえば、

Sub AAA()
On Error Goto ErrorHandler

1000:
' code
1100:
' more code
1200:
' even more code that causes an error
1300:
' yet more code
9999: ' end of main part of procedure
ErrorHandler:
If Err.Number <> 0 Then
   Debug.Print "Error: " + CStr(Err.Number), Err.Descrption, _
      "Last Successful Line: " + CStr(Erl)
End If   
End Sub 

Erl関数は、最後に検出された数値行ラベルを返します。上記の例では、ラベル1200:の後、1300:の前にランタイムエラーが発生すると、Erl関数は1200を返します。ラインラベル。エラー処理ブロックのすぐ上に行ラベルを配置することをお勧めします。通常、9999を使用して、手順の主要部分が予想される痙攣に陥ったことを示します。

ノート:

  • 行ラベルは正の整数でなければなりません-MadeItHere:のようなラベルはErlによって再認識されません。

  • 行ラベルは、VBIDE CodeModuleの実際の行番号とはまったく関係ありません。任意の正数を任意の順序で使用できます。上記の例では、コードは25行程度ですが、行ラベル番号は1000で始まります。エディターの行番号とErlで使用される行ラベル番号の間に関係はありません。

  • 行ラベル番号は特定の順序である必要はありませんが、昇順、上から下にない場合、Erlの有効性と利点は大幅に減少しますが、Erlは引き続き報告します正しい番号。

  • 行ラベルは、表示される手順に固有です。プロシージャProcAがプロシージャProcBを呼び出し、ProcBでエラーが発生し、制御がProcAに戻される場合、ErlProcA)は、ProcAを呼び出す前に、ProcBで最後に検出された行ラベル番号を返します。 ProcA内から、ProcBに表示される可能性のある行ラベル番号を取得できません。

ループ内に行番号ラベルを配置するときは注意してください。例えば、

For X = 1 To 100
500:
' some code that causes an error
600:
Next X

行ラベル500に続くが600の前のコードでエラーが発生し、そのエラーがループの20回目の繰り返しで発生する場合、Erl500を返します600は、ループの前の19回の繰り返しで正常に検出されました。

プロシージャ内の行ラベルの適切な配置は、Erl関数を使用して真に意味のある情報を取得するために重要です。

ネットには、プロシージャに数値行ラベルを自動的に挿入する任意の数の無料ユーティリティがあるため、開発およびデバッグ中に詳細なエラー情報を取得し、コードが公開されたらそれらのラベルを削除します。

予期しないエラーが発生した場合にコードがエンドユーザーにエラー情報を表示する場合、その情報にErlの値を指定すると、Erlの値がそうでない場合よりもVASTLY問題を見つけて修正できます報告。

3
Chip Pearson

これはかなりまともなパターンです。

デバッグの場合:エラーが発生したら、Ctrl-Break(またはCtrl-Pause)を押し、ブレークマーカー(またはそれが呼び出されたもの)をResume行までドラッグし、F8を押すと、「投げた」行にステップします。エラー。

ExitHandlerは「最終的に」です。

砂時計は毎回殺されます。ステータスバーのテキストは毎回クリアされます。

Public Sub ErrorHandlerExample()
    Dim dbs As DAO.Database
    Dim rst As DAO.Recordset

    On Error GoTo ErrHandler
    Dim varRetVal As Variant

    Set dbs = CurrentDb
    Set rst = dbs.OpenRecordset("SomeTable", dbOpenDynaset, dbSeeChanges + dbFailOnError)

    Call DoCmd.Hourglass(True)

    'Do something with the RecordSet and close it.

    Call DoCmd.Hourglass(False)

ExitHandler:
    Set rst = Nothing
    Set dbs = Nothing
    Exit Sub

ErrHandler:
    Call DoCmd.Hourglass(False)
    Call DoCmd.SetWarnings(True)
    varRetVal = SysCmd(acSysCmdClearStatus)

    Dim errX As DAO.Error
    If Errors.Count > 1 Then
       For Each errX In DAO.Errors
          MsgBox "ODBC Error " & errX.Number & vbCrLf & errX.Description
       Next errX
    Else
        MsgBox "VBA Error " & Err.Number & ": " & vbCrLf & Err.Description & vbCrLf & "In: Form_MainForm", vbCritical
    End If

    Resume ExitHandler
    Resume

End Sub



    Select Case Err.Number
        Case 3326 'This Recordset is not updateable
            'Do something about it. Or not...
        Case Else
            MsgBox "VBA Error " & Err.Number & ": " & vbCrLf & Err.Description & vbCrLf & "In: Form_MainForm", vbCritical
    End Select

また、DAOエラーとVBAエラーの両方をトラップします。特定のErr番号をトラップする場合は、VBAエラーセクションに選択ケースを配置できます。

Select Case Err.Number
    Case 3326 'This Recordset is not updateable
        'Do something about it. Or not...
    Case Else
        MsgBox "VBA Error " & Err.Number & ": " & vbCrLf & Err.Description & vbCrLf & "In: Form_MainForm", vbCritical
End Select
3

象のトラップに注意してください:

この議論ではこれについて言及していません。 [アクセス2010]

ACCESS/VBAがCLASSオブジェクトのエラーを処理する方法は、構成可能なオプションによって決定されます。

VBAコードエディター>ツール>オプション>全般>エラートラップ:

enter image description here

2
JoeRobbins

中央エラー処理アプローチと呼ばれる次のものが最適に機能すると思います。

利点

アプリケーションを実行する2つのモードがあります:DebugおよびProductionデバッグモードでは、コードは予期しないエラーで停止し、F8キーを2回押すことで発生した行にジャンプすることで簡単にデバッグできます。 Productionモードでは、意味のあるエラーメッセージがユーザーに表示されます。

次のような意図的なエラーをスローできます。これにより、ユーザーへのメッセージとともにコードの実行が停止します。

Err.Raise vbObjectError, gsNO_DEBUG, "Some meaningful error message to the user"

Err.Raise vbObjectError, gsUSER_MESSAGE, "Some meaningful non-error message to the user"

'Or to exit in the middle of a call stack without a message:
Err.Raise vbObjectError, gsSILENT

実装

次のヘッダーとフッターを使用して、すべてのサブルーチンと関数を大量のコードで「ラップ」し、すべてのエントリポイントでehCallTypeEntryPointを指定する必要があります。 msModule定数にも注意してください。これはすべてのモジュールに配置する必要があります。

Option Explicit
Const msModule As String = "<Your Module Name>"

' This is an entry point 
Public Sub AnEntryPoint()
    Const sSOURCE As String = "AnEntryPoint"
    On Error GoTo ErrorHandler

    'Your code

ErrorExit:
    Exit Sub

ErrorHandler:
    If CentralErrorHandler(Err, ThisWorkbook, msModule, sSOURCE, ehCallTypeEntryPoint) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub

' This is any other subroutine or function that isn't an entry point
Sub AnyOtherSub()
    Const sSOURCE As String = "AnyOtherSub"
    On Error GoTo ErrorHandler

    'Your code

ErrorExit:
    Exit Sub

ErrorHandler:
    If CentralErrorHandler(Err, ThisWorkbook, msModule, sSOURCE) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub

中央エラーハンドラーモジュールの内容は次のとおりです。

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Comments: Error handler code.
'
'           Run SetDebugMode True to use debug mode (Dev mode)
'           It will be False by default (Production mode)
'
' Author:   Igor Popov
' Date:     13 Feb 2014
' Licence:  MIT
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

Option Explicit
Option Private Module

Private Const msModule As String = "MErrorHandler"

Public Const gsAPP_NAME As String = "<You Application Name>"

Public Const gsSILENT As String = "UserCancel"  'A silent error is when the user aborts an action, no message should be displayed
Public Const gsNO_DEBUG As String = "NoDebug"   'This type of error will display a specific message to the user in situation of an expected (provided-for) error.
Public Const gsUSER_MESSAGE As String = "UserMessage" 'Use this type of error to display an information message to the user

Private Const msDEBUG_MODE_COMPANY = "<Your Company>"
Private Const msDEBUG_MODE_SECTION = "<Your Team>"
Private Const msDEBUG_MODE_VALUE = "DEBUG_MODE"

Public Enum ECallType
    ehCallTypeRegular = 0
    ehCallTypeEntryPoint
End Enum

Public Function DebugMode() As Boolean
    DebugMode = CBool(GetSetting(msDEBUG_MODE_COMPANY, msDEBUG_MODE_SECTION, msDEBUG_MODE_VALUE, 0))
End Function

Public Sub SetDebugMode(Optional bMode As Boolean = True)
    SaveSetting msDEBUG_MODE_COMPANY, msDEBUG_MODE_SECTION, msDEBUG_MODE_VALUE, IIf(bMode, 1, 0)
End Sub

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Comments: The central error handler for all functions
'           Displays errors to the user at the entry point level, or, if we're below the entry point, rethrows it upwards until the entry point is reached
'
'           Returns True to stop and debug unexpected errors in debug mode.
'
'           The function can be enhanced to log errors.
'
' Date          Developer           TDID    Comment
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' 13 Feb 2014   Igor Popov                  Created

Public Function CentralErrorHandler(ErrObj As ErrObject, Wbk As Workbook, ByVal sModule As String, ByVal sSOURCE As String, _
                                    Optional enCallType As ECallType = ehCallTypeRegular, Optional ByVal bRethrowError As Boolean = True) As Boolean

    Static ssModule As String, ssSource As String
    If Len(ssModule) = 0 And Len(ssSource) = 0 Then
        'Remember the module and the source of the first call to CentralErrorHandler
        ssModule = sModule
        ssSource = sSOURCE
    End If
    CentralErrorHandler = DebugMode And ErrObj.Source <> gsNO_DEBUG And ErrObj.Source <> gsUSER_MESSAGE And ErrObj.Source <> gsSILENT
    If CentralErrorHandler Then
        'If it's an unexpected error and we're going to stop in the debug mode, just write the error message to the immediate window for debugging
        Debug.Print "#Err: " & Err.Description
    ElseIf enCallType = ehCallTypeEntryPoint Then
        'If we have reached the entry point and it's not a silent error, display the message to the user in an error box
        If ErrObj.Source <> gsSILENT Then
            Dim sMsg As String: sMsg = ErrObj.Description
            If ErrObj.Source <> gsNO_DEBUG And ErrObj.Source <> gsUSER_MESSAGE Then sMsg = "Unexpected VBA error in workbook '" & Wbk.Name & "', module '" & ssModule & "', call '" & ssSource & "':" & vbCrLf & vbCrLf & sMsg
            MsgBox sMsg, vbOKOnly + IIf(ErrObj.Source = gsUSER_MESSAGE, vbInformation, vbCritical), gsAPP_NAME
        End If
    ElseIf bRethrowError Then
        'Rethrow the error to the next level up if bRethrowError is True (by Default).
        'Otherwise, do nothing as the calling function must be having special logic for handling errors.
        Err.Raise ErrObj.Number, ErrObj.Source, ErrObj.Description
    End If
End Function

デバッグモードに設定するには、イミディエイトウィンドウで次のコマンドを実行します。

SetDebugMode True
2
igorsp7

このスレッドの前半で行われた声明に関する私の個人的な見解:

そしてただの楽しみのために:

On Error Resume Nextは悪魔の化身であり、エラーを静かに隠すため、避けるべきです。

エラーを発生させて作業を停止させたくない場合、およびステートメントが前のステートメントの結果に依存しない場合は、On Error Resume Nextを使用しています。

これを行うとき、グローバル変数debugModeOnを追加し、Trueに設定します。次に、この方法で使用します。

If not debugModeOn Then On Error Resume Next

作業を配信するときに、変数をfalseに設定して、エラーをユーザーのみに非表示にし、テスト中にエラーを表示します。

空の可能性があるListObjectのDataBodyRangeを呼び出すなど、失敗する可能性のある操作を行うときにも使用します。

On Error Resume Next
Sheet1.ListObjects(1).DataBodyRange.Delete
On Error Goto 0

の代わりに:

If Sheet1.ListObjects(1).ListRows.Count > 0 Then 
    Sheet1.ListObjects(1).DataBodyRange.Delete
End If

または、コレクション内のアイテムの存在を確認します。

On Error Resume Next
Err.Clear
Set auxiliarVar = collection(key)

' Check existence (if you try to retrieve a nonexistant key you get error number 5)
exists = (Err.Number <> 5)
1
Jordi