简体   繁体   English

VBA 错误处理有哪些好的模式?

[英]What are some good patterns for VBA error handling?

What are some good patterns for error handling in VBA? VBA 中的错误处理有哪些好的模式?

In particular, what should I do in this situation:特别是在这种情况下我应该怎么做:

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

I want to handle both errors, and resume execution after the code where the error may occur.我想处理这两个错误,并在可能发生错误的代码之后恢复执行。 Also, the finally code at the end must always run - no matter what exceptions are thrown earlier.此外,最后的 finally 代码必须始终运行 - 无论之前抛出什么异常。 How can I achieve this outcome?我怎样才能达到这个结果?

Error Handling in VBA VBA中的错误处理


  • On Error Goto ErrorHandlerLabel On Error Goto ErrorHandlerLabel
  • Resume ( Next | ErrorHandlerLabel ) ResumeNext | ErrorHandlerLabel
  • On Error Goto 0 (disables current error handler) On Error Goto 0 (禁用当前错误处理程序)
  • Err object Err对象

The Err object's properties are normally reset to zero or a zero-length string in the error handling routine, but it can also be done explicitly with Err.Clear . 通常,在错误处理例程中,将Err对象的属性重置为零或长度为零的字符串,但是也可以使用Err.Clear明确地将其Err.Clear

Errors in the error handling routine are terminating. 错误处理例程中的错误正在终止。

The range 513-65535 is available for user errors. 513-65535范围适用于用户错误。 For custom class errors, you add vbObjectError to the error number. 对于自定义类错误,请将vbObjectError添加到错误号。 See MS documentation about Err.Raise and the list of error numbers . 请参阅有关Err.Raise MS文档和错误编号列表

For not implemented interface members in a derived class, you should use the constant E_NOTIMPL = &H80004001 . 对于派生类中未实现的接口成员,应使用常量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

I would also add: 我还要补充:

  • The global Err object is the closest you have to an exception object 全局Err对象与异常对象最接近
  • You can effectively "throw an exception" with Err.Raise 您可以使用Err.Raise有效地“引发异常”

And just for fun: 只是为了好玩:

  • On Error Resume Next is the devil incarnate and to be avoided, as it silently hides errors On Error Resume Next是魔鬼的化身,应该避免,因为它无声地隐藏了错误

So you could do something like this 所以你可以做这样的事情

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

If you want to bake in custom exceptions. 如果要烘烤自定义异常。 (eg ones that violate business rules) use the example above but use the goto to alter the flow of the method as necessary. (例如,违反业务规则的规则)使用上面的示例,但根据需要使用goto更改方法的流程。

Here's my standard implementation. 这是我的标准实现。 I like the labels to be self-descriptive. 我喜欢标签具有自我描述性。

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

Or, with a Finally block: 或者,使用Finally块:

Public Sub DoSomething()

    On Error GoTo Catch ' Try

    ' normal code here

    GoTo Finally
Catch:

    'error code

Finally:

    'cleanup code

End Sub

Professional Excel Development has a pretty good error handling scheme . 专业Excel开发有一个很好的错误处理方案 If you're going to spend any time in VBA, it's probably worth getting the book. 如果您打算花任何时间在VBA上,那么值得一本书。 There are a number of areas where VBA is lacking and this book has good suggestions for managing those areas. VBA缺少许多领域,并且本书为管理这些领域提供了很好的建议。

PED describes two error handling methods. PED描述了两种错误处理方法。 The main one is a system where all entry point procedures are subprocedures and all other procedures are functions that return Booleans. 主要的是一个系统,其中所有入口点过程都是子过程,而所有其他过程都是返回布尔值的函数。

The entry point procedure use On Error statements to capture errors pretty much as designed. 入口点过程使用On Error语句按设计捕获错误。 The non-entry point procedures return True if there were no errors and False if there were errors. 如果没有错误,则非入口点过程将返回True;如果存在错误,则将返回False。 Non-entry point procedures also use On Error. 非入口点过程也使用On Error。

Both types of procedures use a central error handling procedure to keep the error in state and to log the error. 两种类型的过程都使用中央错误处理过程来使错误保持状态并记录错误。

I use a piece of code that i developed myself and it is pretty good for my codes: 我使用自己开发的一段代码,这对我的代码非常有用:

In the beginning of the function or sub, I define: 在函数或子程序的开头,我定义:

On error Goto ErrorCatcher:

and then, I handle the possible errors 然后,我处理可能的错误

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

Here's a pretty decent pattern. 这是一个相当不错的模式。

For debugging: When an error is raised, hit Ctrl-Break (or Ctrl-Pause), drag the break marker (or whatever it's called) down to the Resume line, hit F8 and you'll step to the line that "threw" the error. 进行调试:出现错误时,按Ctrl-Break(或Ctrl-Pause),将中断标记(或任何它所称的名称)向下拖动到Resume行,单击F8,然后您将跳至“抛出”的行错误。

The ExitHandler is your "Finally". ExitHandler是您的“最终”。

Hourglass will be killed every time. 沙漏每次都会被杀死。 Status bar text will be cleared every time. 每次都会清除状态栏文本。

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

It also traps for both DAO and VBA errors. 它还会捕获DAO和VBA错误。 You can put a Select Case in the VBA error section if you want to trap for specific Err numbers. 如果要捕获特定的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

The code below shows an alternative that ensures there is only one exit point for the sub/function. 下面的代码显示了一种替代方法,可确保子/功能只有一个出口点。

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

Also relevant to the discussion is the relatively unknown Erl function. 与讨论相关的还有相对未知的Erl函数。 If you have numeric labels within your code procedure, eg, 如果您的代码过程中包含数字标签,例如,

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 

The Erl function returns the most recently encountered numberic line label. Erl函数返回最近遇到的数字行标签。 In the example above, if a run-time error occurs after label 1200: but before 1300: , the Erl function will return 1200 , since that is most recenlty sucessfully encountered line label. 在上面的示例中,如果在标签1200:但在1300:之前发生运行时错误,则Erl函数将返回1200 ,因为这最成功地遇到了行标签。 I find it to be a good practice to put a line label immediately above your error handling block. 我发现将行标签放在错误处理块的正上方是一个好习惯。 I typcially use 9999 to indicate that the main part of the procuedure ran to its expected conculsion. 我通常使用9999表示程序的主要部分已达到预期的效果。

NOTES: 笔记:

  • Line labels MUST be positive integers -- a label like MadeItHere: isn't recogonized by Erl . 线标签必须是正整数-像MadeItHere:这样的标签不会由Erl MadeItHere:标注。

  • Line labels are completely unrelated to the actual line numbers of a VBIDE CodeModule . 行标签与VBIDE CodeModule的实际行号完全无关。 You can use any positive numbers you want, in any order you want. 您可以按任意顺序使用所需的任何正数。 In the example above, there are only 25 or so lines of code, but the line label numbers begin at 1000 . 在上面的示例中,只有25行左右的代码行,但是行标签号从1000开始。 There is no relationship between editor line numbers and line label numbers used with Erl . Erl使用的编辑器行号和行标签号之间没有关系。

  • Line label numbers need not be in any particular order, although if they are not in ascending, top-down order, the efficacy and benefit of Erl is greatly diminished, but Erl will still report the correct number. 线标签编号不必按任何特定顺序排列,尽管如果它们不是按升序,自上而下的顺序排列,则Erl的功效和益处会大大降低,但是Erl仍会报告正确的编号。

  • Line labels are specific to the procedure in which they appear. 线标签特定于它们出现的过程。 If procedure ProcA calls procedure ProcB and an error occurs in ProcB that passes control back to ProcA , Erl (in ProcA ) will return the most recently encounterd line label number in ProcA before it calls ProcB . 如果程序ProcA调用过程ProcB并在发生错误ProcB将控制回ProcAErl (在ProcA )将返回最近encounterd行标签号ProcA它调用之前ProcB From within ProcA , you cannot get the line label numbers that might appear in ProcB . ProcA ,您将无法获得可能出现在ProcB的行标签号。

Use care when putting line number labels within a loop. 将行号标签放入循环中时请多加注意。 For example, 例如,

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

If the code following line label 500 but before 600 causes an error, and that error arises on the 20th iteration of the loop, Erl will return 500 , even though 600 has been encounterd successfully in the previous 19 interations of the loop. 如果行标签500600之前的代码导致错误,并且该错误在循环的第20次迭代中发生,则Erl将返回500 ,即使在循环的前19个交互中成功遇到了600

Proper placement of line labels within the procedure is critical to using the Erl function to get truly meaningful information. 在过程中正确放置线标签对于使用Erl函数获取真正有意义的信息至关重要。

There are any number of free utilies on the net that will insert numeric line label in a procedure automatically, so you have fine-grained error information while developing and debugging, and then remove those labels once code goes live. 网络上有任意数量的免费实用程序,它们会自动在过程中插入数字行标签,因此在开发和调试时,您可以获得细粒度的错误信息,然后在代码上线后删除这些标签。

If your code displays error information to the end user if an unexpected error occurs, providing the value from Erl in that information can make finding and fixing the problem VASTLY simpler than if value of Erl is not reported. 如果您的代码在发生意外错误时向最终用户显示错误信息,则提供Erl中的值可以使查找和解决问题的方式比未报告Erl情况简单得多。

Beware the elephant trap: 当心大象陷阱:

I saw no mention of this in this discussion. 我在讨论中没有提到这一点。 [Access 2010] [Access 2010]

How ACCESS/VBA handles errors in CLASS objects is determined by a configurable option: ACCESS / VBA如何处理CLASS对象中的错误由一个可配置的选项决定:

VBA Code Editor > Tools > Options > General > Error Trapping: VBA代码编辑器>工具>选项>常规>错误捕获:

在此处输入图片说明

I find the following to work best, called the central error handling approach. 我发现以下方法最有效,称为中央错误处理方法。

Benefits 好处

You have 2 modes of running your application: Debug and Production . 您有两种运行应用程序的模式: DebugProduction In the Debug mode, the code will stop at any unexpected error and allow you to debug easily by jumping to the line where it occurred by pressing F8 twice. 在“ 调试”模式下,代码将在出现任何意外错误时停止,并允许您通过按两次F8跳到发生错误的行来轻松调试。 In the Production mode, a meaningful error message will get displayed to the user. 生产模式下,有意义的错误消息将显示给用户。

You can throw intentional errors like this, which will stop execution of the code with a message to the user: 您可以抛出这样的故意错误,这将停止执行代码并向用户发送消息:

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

Implementation 实作

You need to "wrap" all subroutines and functions with any significant amount of code with the following headers and footers, making sure to specify ehCallTypeEntryPoint in all your entry points. 您需要使用大量的代码和以下标头和页脚来“包装”所有子例程和函数,并确保在所有入口点中指定ehCallTypeEntryPoint Note the msModule constant as well, which needs to be put in all modules. 还要注意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

The contents of the central error handler module is the following: 中央错误处理程序模块的内容如下:

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' 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

To set yourself in the Debug mode, run the following in the Immediate window: 要将自己设置为“ 调试”模式,请在“立即”窗口中运行以下命令:

SetDebugMode True

My personal view on a statement made earlier in this thread: 我对此线程前面的声明的个人看法:

And just for fun: 只是为了好玩:

On Error Resume Next is the devil incarnate and to be avoided, as it silently hides errors. On Error Resume Next是魔鬼的化身,要避免,因为它无声地隐藏了错误。

I'm using the On Error Resume Next on procedures where I don't want an error to stop my work and where any statement does not depend on the result of the previous statements. 我在不想让错误停止我的工作并且任何语句不依赖于先前语句结果的过程中使用“ On Error Resume NextOn Error Resume Next

When I'm doing this I add a global variable debugModeOn and I set it to True . 在执行此操作时,我添加了一个全局变量debugModeOn并将其设置为True Then I use it this way: 然后我用这种方式:

If not debugModeOn Then On Error Resume Next

When I deliver my work, I set the variable to false, thus hiding the errors only to the user and showing them during testing. 在交付工作时,我将变量设置为false,从而仅将错误隐藏给用户并在测试过程中显示。

Also using it when doing something that may fail like calling the DataBodyRange of a ListObject that may be empty: 在执行可能会失败的事情(例如,调用可能为空的ListObject的DataBodyRange)时也使用它:

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

Instead of: 代替:

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

Or checking existence of an item in a collection: 或检查集合中某项的存在:

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)

After 25 years of VBA development, error-handling has always been a bother -- until now.经过 25 年的 VBA 开发,错误处理一直是一个麻烦——直到现在。 I've experimented with various techniques, and this is my favorite.我尝试了各种技术,这是我最喜欢的。

This approach brings together the best ideas on this page, plus a few of my own techniques.这种方法汇集了本页上最好的想法,以及我自己的一些技巧。

The question asker mentions only the simple case -- a single procedure.提问者只提到了一个简单的案例——一个单一的程序。 He doesn't talk about sub-procedures, custom errors, logging, error-related processing, and other vital error-related topics in professional VBA programming.他没有谈论子过程、自定义错误、日志记录、错误相关处理以及专业 VBA 编程中其他重要的错误相关主题。 I'll cover more than the simple case.我将介绍的不仅仅是简单的案例。

No error-handling无错误处理

The simplest case: Don't assume you always need handling.最简单的情况:不要假设您总是需要处理。 Procedures which are never going to error out don't need error-handling.永远不会出错的过程不需要错误处理。

Ignored Errors忽略的错误

It's acceptable to simply ignore some errors.简单地忽略一些错误是可以接受的。 This is perfectly acceptable example of an ignored error, because you know there's no other error that can reasonably occur on that statement.这是一个完全可以接受的忽略错误示例,因为您知道在该语句上没有其他错误可以合理地发生。

...
On Error Resume Next
Set bkCars = Workbooks("Cars.xlsx")
On Error GoTo 0
If (bkCars Is Nothing) Then MsgBox "Cars workbook isn't open."
Set bkCars = Workbooks("Wheelbarrows.xlsx")
...

I've never heard of any other error ever happening on that statement.我从未听说过在该语句中发生过任何其他错误。 Use your judgement.使用你的判断。 Ignore extremists.无视极端分子。 VBA is supposed to be easy. VBA应该很容易。 On Error Resume Next isn't "the Devil incarnate"; On Error Resume Next不是“魔鬼的化身”; it's one way to implement Try..Catch in VBA.这是在 VBA 中实现 Try..Catch 的一种方法。 For more examples, see Jordi's answer .有关更多示例,请参阅Jordi 的回答

Unhandled Errors未处理的错误

The remainder of this answer is about unhandled errors.这个答案的其余部分是关于未处理的错误。 An unhandled error is an unexpected error which breaks your program.未处理的错误是会破坏程序的意外错误。

Handler处理程序

Here's my basic handler:

Sub Main()
          On Error GoTo HANDLER

          Dim x As Long
          x = "hi"

HANDLER:
          ' cleanup
          x = 0

          ' error-handler
          If (Err = 0) Then Exit Sub
          MsgBox Error
End Sub
  • Flow-through: Inspired by @NickD and others here, it completely eliminates "Exit Sub" and "Resume" from your code. Flow-through:受@NickD 和其他人的启发,它从你的代码中完全消除了“退出子”和“恢复”。 Code flows in one direction, instead of jumping around.代码流向一个方向,而不是四处跳跃。 There's a single exit point in the procedure.过程中有一个出口点。 All are important, if you like less typing, less code, and less spaghetti.如果你喜欢更少的打字、更少的代码和更少的意大利面,所有这些都很重要。
  • * Cleanup: This approach ensures the same cleanup code runs whether there is or isn't an error. *清理:此方法可确保无论是否有错误都运行相同的清理代码。 Error and non-error conditions share the same cleanup code.错误和非错误条件共享相同的清理代码。 Straightforward pattern handles a wide variety of scenarios regarding cleanup and custom-handling.简单的模式处理有关清理和自定义处理的各种场景。

Convenience方便

Make your life easier.让您的生活更轻松。

Function IsEr() As Boolean
      IsEr = (Err <> 0)
      ' or IsEr = CBool(Err)
End Function

Special Handlers特殊处理程序

The basic style can handle more complex cases.基本样式可以处理更复杂的情况。 For example, you can insert handling or cleanup for specific errors.例如,您可以为特定错误插入处理或清理。

          ...
HANDLER:
          If Not IsEr Then Exit Sub
          If (Err = 11) Then Call_TheBatphone
          MsgBox Error
End Sub

Procedure Calls, No Cleanup过程调用,无需清理

A called procedure which doesn't have any special cleanup code doesn't need any error-code.没有任何特殊清理代码的被调用过程不需要任何错误代码。 It's errors, and those of it's sub-procedures, will automatically bubble up to the entry-procedure.它的错误及其子过程的错误将自动冒泡到入口过程。 You can have cleanup-code at each sub.您可以在每个子程序中使用清理代码。

Sub Main()
          On Error GoTo HANDLER
          Sub_1
HANDLER:
          If Not IsEr Then Exit Sub
          MsgBox Error
End Sub


Sub Sub_1()
          Dim x
          x = 5/0    <.. will jump to Main HANDLER
End Sub

Procedure Calls, Cleanup过程调用、清理

However, a sub-procedure which must always run cleanup-code (even in case of an error) needs a bit of extra help.但是,必须始终运行清理代码(即使出现错误)的子过程需要一些额外的帮助。 The sub's error-handler resets the error-event, so the error must be retriggered with Err.Raise . sub 的错误处理程序重置错误事件,因此必须with Err.Raise重新触发错误。

This means your handler for subs must be different than the handler for the kickoff-procedure (aka "entry-point", meaning the first procedure that runs at the beginning of the roundtrip code-loop).这意味着您的 subs 处理程序必须与启动过程(又名“入口点”,意思是在往返代码循环开始时运行的第一个过程)的处理程序不同

Sub-handlers shouldn't show any message boxes or do any logging -- that should remain with the Main handler.子处理程序不应该显示任何消息框或进行任何日志记录——这应该保留在主处理程序中。 Sub handlers should only be used for special cleanup, special processing, and to append extra error-info to the error object.子处理程序应仅用于特殊清理、特殊处理,以及将额外的错误信息附加到错误对象。

Sub Main()
          On Error GoTo HANDLER
          Sub_1
HANDLER:
          If Not IsEr Then Exit Sub
          MsgBox Error
End Sub


Sub Sub_1()
          On Error GoTo HANDLER
          Dim x
          x = 5/0
          ' More processing here
HANDLER:
          If Not IsEr Then Exit Sub
          Err.Raise Err.Number, Err.Source, Err.Description & vbNewline & "Some problem with divisor"
End Sub

Run

Beware: any procedure executed with the Run statement requires special handling.注意:使用 Run 语句执行的任何过程都需要特殊处理。 If you raise an error within the procedure, the error will not bubble up to the entry procedure, and whatever into the Raise puts into the Err will be lost when execution returns to the caller.如果在过程中引发错误,错误将不会冒泡到入口过程,并且当执行返回到调用者时,Raise 中放入 Err 的任何内容都将丢失。 Therefore, you need to create a workaround.因此,您需要创建一个解决方法。 My workaround is to put Err.Number into a global variable, and then on return from the Run, check that variable.我的解决方法是将Err.Number放入一个全局变量中,然后在从 Run 返回时检查该变量。

Public lErr As Long


Sub Main()
          On Error GoTo HANDLER
          Run "Sub_1"

          If (lErr <> 0) then Err.Raise lErr

          Dim x
          x = 5
HANDLER:
          If Not IsEr Then Exit Sub
          MsgBox Error
End Sub


Sub Sub_1()
          On Error Goto HANDLER

          Dim x
          ' will NOT jump to Main HANDLER, since Run
          x = 5/0
HANDLER:
          If (Err <> 0) Then lErr = Err
End Sub

Alerts警报

If your intention is produce professional code, then you must communicate all unexpected errors appropriately to the user, as shown above.如果您的意图是生成专业代码,那么您必须适当地向用户传达所有意外错误,如上所示。 You never want users to see a "Debug" button or find themselves dropped into VBA.您永远不希望用户看到“调试”按钮或发现自己陷入了 VBA。

Centralized Handling集中处理

The next evolution is centralized handling.下一个演变是集中处理。 This gives you a really quick and easy way to replicate your perfect error-handling everywhere.这为您提供了一种非常快速简便的方法,可以在任何地方复制完美的错误处理。 As mentioned by @igorsp7, centralized handling makes it simpler and easier to implement consistent, reliable error-handling everywhere.正如@igorsp7 所提到的,集中处理使得在任何地方实现一致、可靠的错误处理变得更简单、更容易。 It makes it easy to reuse complex handler logic.它使重用复杂的处理程序逻辑变得容易。 It is so easy and simple to just place ErrorHandler at the bottom of every procedure.ErrorHandler放在每个过程的底部是非常容易和简单的。 Reminder: Err is a global object, so there's no need to pass it around as an argument.提醒: Err是一个全局对象,因此无需将其作为参数传递。

Sub Main()
          On Error GoTo HANDLER
          Sub_1
HANDLER:
          MainCleanup
          ErrorHandler_Entry
End Sub


Sub Sub_1()
          On Error GoTo HANDLER
          Dim x
          x = 5/0
HANDLER:
          SubCleanup
          ErrorHandler_Sub
End Sub


Sub ErrorHandler_Entry()
          If Not IsEr Then Exit Sub

          ' log error to a file for developer to inspect.
          Log_Error_To_File

          ' Then alert user. InputBox provides simple way to let users copy with mouse
          InputBox "Sorry, something went haywire. Please inform the developer or owner of this application.", _
                    "Robot Not Working", Err.Number & vbNewLine & Err.Source & vbNewLine & Err.Description
End Sub


Private Sub ErrorHandler_Sub()
          If Not IsEr Then Exit Sub

          ' bubble up the error to the next caller
          Err.Raise Err.Number, Err.Source, Err.Description
End Sub

Custom Errors自定义错误

Numbering编号

Use = vbObjectError + 514 for your first one, as 1 to 513 are reserved for native VB errors.第一个使用 = vbObjectError + 514,因为 1 到 513 是为本地 VB 错误保留的。 I'm still researching custom error numbering.我仍在研究自定义错误编号。 There's a lot of conflicting information.有很多相互矛盾的信息。 It may be simply它可能只是

  • Native errors are positive integers, to 65535?本机错误是正整数,到 65535?
  • Custom errors are negative integers, 0 to -2,000,000,000?自定义错误是负整数,0 到 -2,000,000,000?

But I don't know yet if that's correct!但我还不知道这是否正确! Your error handlers will work even if you use native error numbers.即使您使用本机错误编号,您的错误处理程序也会工作。 However, if your error handling is based on whether it's a native vs custom error, or if your application is reporting the error to a developer, then to avoid confusion or more bugs, the best practice is to not reuse native numbers.但是,如果您的错误处理是基于它是本地错误还是自定义错误,或者您的应用程序是否向开发人员报告错误,那么为了避免混淆或更多错误,最佳做法是不要重用本地数字。

Syntax句法

Enum CustomError
          UserPause = vbObjectError + 514
          UserTerminate
End Enum


Function CustomErr()as Boolean
          CustomErr = (Err >= 514)
End Function


Sub Test
          On Error Goto HANDLER
          Err.Raise CustomError.UserPause
HANDLER:
          Cleanup
          If CustomErr Then Handle_CustomError
End Sub


Sub Handle_CustomError()
          Select Case Err
                    Case UserPause
                              MsgBox "Paused"
                              Resume Next
                    Case UserTerminate
                              SpecialProcessing
                              MsgBox "Terminated"
                              End
          End Select
End Sub

Error Categories:错误类别:

You may want custom errors in an addin, an application workbook, and a data workbook.您可能需要在插件、应用程序工作簿和数据工作簿中自定义错误。 You should reserve a range of allowed error numbers for each type.您应该为每种类型保留一系列允许的错误编号。 Then your handlers can determine the source of the error by its number.然后您的处理程序可以通过其编号确定错误的来源。 This enum uses the starting number for each range.此枚举使用每个范围的起始编号。

Enum AppError
          UserPause = vbObjectError + 514
          UserTerminate
End Enum


Enum AddinError
          LoadFail = vbObjectError + 1000
End Enum


Enum DataError
          DatabaseLocked = vbObjectError + 1500
End Enum


Enum ErrorType
    VB
    App
    Addin
    Data
End Enum


Function Get_ErrorCategory() As ErrorType
          If (Err < 514) Then
                    Get_ErrorCategory = VB

          ElseIf (Err <= 1000) Then
                    Get_ErrorCategory = App

          ElseIf (Err <= 1500) Then
                    Get_ErrorCategory = Addin

          Else
                    Get_ErrorCategory = Data
          End If
End Function


Sub ErrorHandler_Entry(Optional sInfo As String)
          If Not IsEr Then Exit Sub

          Select Case Get_ErrorCategory
                    Case VB
                              InputBox "Sorry, something went haywire. Please inform the developer or owner of this application.", _
                                        "Robot Not Working", Err.Number & vbNewLine & Err.Source & vbNewLine & Err.Description & vbNewLine & sInfo

                    Case Addin
                              Log_Error_To_File

                    Case Data
                              ' do nothing
          End Select
End Sub

Developer Mode开发者模式

As developer, you'll want to debug unhandled errors, instead of getting friendly messages.作为开发人员,您需要调试未处理的错误,而不是获得友好的消息。 So you want to temporarily disable your handler when you're in development.因此,您希望在开发过程中暂时禁用处理程序。 That's conveniently done by manually setting a "Debug" state someplace.这可以通过在某处手动设置“调试”状态来方便地完成。 There are a couple of ways to do it:有几种方法可以做到:

Custom "ExecMode":自定义“执行模式”:

Get_DebugMode is a function that you need to write, which pulls your Debug mode from wherever you stored it. Get_DebugMode是您需要编写的函数,它可以从您存储的任何位置提取调试模式。 Can be stored in an Excel defined-name, a module constant, a worksheet cell -- whatever you prefer.可以存储在 Excel 定义的名称、模块常量、工作表单元格中——无论您喜欢什么。

      ...
      If Not Get_DebugMode Then _
                On Error GoTo HANDLER
      ...

Conditional Compilation Arguments:条件编译参数:

This needs to be applied in the VB IDE.这需要在VB IDE中应用。

      ...
      #If Not DEBUGMODE Then _
                On Error GoTo HANDLER
      ...

在此处输入图片说明

Changing code behavior at compile time 在编译时更改代码行为

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM