代码之家  ›  专栏  ›  技术社区  ›  jwoolard

vba错误处理的良好模式

  •  67
  • jwoolard  · 技术社区  · 15 年前

    在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) ...
    

    我想处理这两个错误,并在可能发生错误的代码之后继续执行。此外,最后的代码必须始终运行-无论前面抛出了什么异常。我如何才能实现这个结果?

    12 回复  |  直到 6 年前
        1
  •  92
  •   Simon Sobisch    7 年前

    VBA中的错误处理


    • On Error Goto 错误处理程序标签
    • Resume ( Next γ 错误处理程序标签 )
    • On Error Goto 0 (禁用当前错误处理程序)
    • Err 对象

    这个 厄尔 对象的属性通常在错误处理例程中重置为零或零长度的字符串,但也可以使用 Err.Clear .

    错误处理例程中的错误正在终止。

    范围513-65535可用于用户错误。 对于自定义类错误,可以添加 vbObjectError 到错误号。 参见MS文档 Err.Raise 以及 list of error numbers .

    对于未实现的接口成员 衍生的 类,应使用常量 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
    
        2
  •  33
  •   Joel Goodwin    15 年前

    我还要补充一点:

    • 全球 Err 对象是最接近异常对象的对象
    • 您可以有效地“抛出异常” Err.Raise

    只是为了好玩:

    • On Error Resume Next 魔鬼是化身吗?应该避免,因为它默默地隐藏着错误。
        3
  •  16
  •   DaveRandom    12 年前

    所以你可以这样做

    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更改方法的流程。

        4
  •  11
  •   LimaNightHawk    10 年前

    这是我的标准实现。我喜欢标签是自我描述的。

    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
    
        5
  •  4
  •   Dick Kusleika    9 年前

    Professional Excel Development 有一个相当不错的 error handling scheme .如果你想在VBA上花点时间,那本书可能值得一读。有许多领域缺乏VBA,本书对管理这些领域有很好的建议。

    PED描述了两种错误处理方法。主要的是一个系统,其中所有入口点过程都是子过程,所有其他过程都是返回布尔值的函数。

    入口点过程在错误语句上使用几乎与设计的一样来捕获错误。如果没有错误,则非入口点过程返回true;如果有错误,则返回false。非入口点程序也在出错时使用。

    这两种类型的过程都使用中央错误处理过程来保持错误状态并记录错误。

        6
  •  3
  •   Thiago Cardoso    10 年前

    我使用一段我自己开发的代码,这对于我的代码来说非常好:

    在函数或子函数的开头,我定义了:

    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
    
        7
  •  3
  •   whistle britches    9 年前

    这是一个相当不错的模式。

    调试:当出现错误时,按ctrl break(或ctrl pause),将break marker(或它调用的任何内容)向下拖动到继续行,按F8,您将进入“抛出”错误的行。

    存在者就是你的“最终”。

    沙漏每次都会被杀死。 状态栏文本将每次清除。

    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错误。如果要对特定的错误号进行补漏白,可以在“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
    
        8
  •  2
  •   nickD    7 年前

    下面的代码显示了一个备选方案,确保子/函数只有一个出口点。

    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
    
        9
  •  2
  •   JonathanDavidArndt    7 年前

    与讨论相关的是相对未知的 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 
    

    这个 厄尔 函数返回最近遇到的数字行标签。在上面的示例中,如果在标签之后出现运行时错误 1200: 但以前 1300: , the 厄尔 函数将返回 1200 ,因为这是最近成功遇到的行标签。我发现在错误处理块的正上方放置一个行标签是一个很好的实践。我典型地使用 9999 表明程序的主要部分达到了预期的冲击。

    笔记:

    • 行标签必须是正整数--类似于标签的 MadeItHere: 不会被 厄尔 .

    • 线条标签与 VBIDE CodeModule . 你可以使用任何你想要的正数,以任何你想要的顺序。在上面的例子中,只有大约25行代码,但是行标签号从 1000 . 编辑器行号和用于的行标签号之间没有关系 厄尔 .

    • 行标签号无需按任何特定顺序排列,但如果不是按升序、自上而下的顺序排列,则其功效和益处 厄尔 大大减少了,但是 厄尔 仍将报告正确的号码。

    • 行标签特定于它们出现的过程。中频过程 ProcA 调用过程 ProcB 普罗布 把控制权交还给 普罗卡 , 厄尔 普罗卡 )将返回最近遇到的行标签号 普罗卡 在它呼叫之前 普罗布 . 从内 普罗卡 ,无法获取可能出现在 普罗布 .

    在循环中放置行号标签时要小心。例如,

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

    如果下面的代码是行标签 500 但以前 600 导致错误,该错误在循环的第20次迭代时出现, 厄尔 将返回 五百 即使 六百 在循环的前19个交互中成功遇到。

    在程序中正确放置行标签对于使用 厄尔 获取真正有意义的信息。

    网络上有任意数量的自由实用程序,它们会自动在过程中插入数字行标签,因此在开发和调试过程中,您可以获得细粒度的错误信息,然后在代码上线后删除这些标签。

    如果代码在发生意外错误时向最终用户显示错误信息,请提供 厄尔 在这些信息中,查找和修复问题的过程比 厄尔 未报告。

        10
  •  1
  •   Jordi    10 年前

    我个人对本主题前面所作声明的看法:

    只是为了好玩:

    在错误恢复时,下一个是魔鬼的化身,应该避免,因为它会悄悄地隐藏错误。

    我正在使用 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)
    
        11
  •  1
  •   CinCout robowen5mac    9 年前

    小心大象陷阱:

    我在这次讨论中没有提到这一点。[访问2010 ]

    access/vba如何处理类对象中的错误由可配置选项决定:

    VBA代码编辑器>工具>选项>常规>错误捕获:

    enter image description here

        12
  •  1
  •   igorsp7    6 年前

    我发现以下方法最有效,称为中央错误处理方法。

    效益

    您有两种运行应用程序的模式: 调试 生产 . 在 调试 在模式下,代码将在任何意外错误处停止,并允许您通过按两次F8键跳到发生错误的行来轻松调试。在 生产 模式,将向用户显示有意义的错误消息。

    您可以抛出这样的故意错误,这样会停止代码的执行,并向用户发送一条消息:

    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