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) ...
我想处理这两个错误,并在可能发生错误的代码之后继续执行。此外,最后的代码必须始终运行 - 无论先前抛出什么异常。我怎样才能实现这一结果?
答案 0 :(得分:96)
On Error Goto
ErrorHandlerLabel Resume
(Next
| ErrorHandlerLabel )On Error Goto 0
(禁用当前错误处理程序)Err
object Err
对象的属性通常在错误处理例程中重置为零或零长度字符串,但也可以使用Err.Clear
显式完成。
错误处理例程中的错误正在终止。
范围513-65535可用于用户错误。
对于自定义类错误,请将vbObjectError
添加到错误编号中。
请参阅有关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
答案 1 :(得分:34)
我还想补充一下:
Err
对象是您与异常对象最接近的对象Err.Raise
只是为了好玩:
On Error Resume Next
是魔鬼的化身并且要避免,因为它默默地隐藏错误答案 2 :(得分:16)
所以你可以做这样的事情
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来改变方法的流程。
答案 3 :(得分:11)
这是我的标准实施。我喜欢标签是自我描述的。
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
答案 4 :(得分:4)
Professional Excel Development非常好error handling scheme。如果您打算在VBA中度过任何时间,那么这本书可能值得一试。 VBA缺乏许多领域,本书对管理这些领域提出了很好的建议。
PED描述了两种错误处理方法。主要的一个系统是所有入口点程序都是子程序,所有其他程序都是返回布尔值的函数。
入口点过程使用On Error语句来捕获与设计相当的错误。如果没有错误,非入口点程序返回True,如果有错误则返回False。非入口点程序也使用On Error。
两种类型的过程都使用中央错误处理过程来保持错误状态并记录错误。
答案 5 :(得分:3)
我使用了自己开发的一段代码,这对我的代码非常有用:
在函数或子函数的开头,我定义:
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
答案 6 :(得分:3)
这是一个相当不错的模式。
用于调试:当出现错误时,按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。
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
答案 7 :(得分:3)
下面的代码显示了一个替代方案,确保子/函数只有一个退出点。
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
答案 8 :(得分:3)
与讨论相关的还有相对未知的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
,则Erl
(在ProcA
中)将返回在ProcA
调用ProcB
之前,ProcA
中最近遇到的行标签号。在ProcB
内,您无法获得For X = 1 To 100
500:
' some code that causes an error
600:
Next X
中可能出现的行标签数字。
将行号标签放入循环中时要小心。例如,
500
如果行标签600
之后但Erl
之前的代码导致错误,并且在循环的第20次迭代中出现该错误,则500
将返回600
,即使Erl
在循环的前19个迭代中已成功遇到。
在程序中正确放置行标签对于使用Erl
函数获取真正有意义的信息至关重要。
网上有任意数量的免费实用程序会自动在程序中插入数字行标签,因此在开发和调试时会有细粒度的错误信息,然后在代码生效后删除这些标签。
如果您的代码在发生意外错误时向最终用户显示错误信息,那么从Erl
中提供该信息中的值可以比{{1}}的值更简单地查找和修复问题没有报道。
答案 9 :(得分:2)
小心大象陷阱:
我在这次讨论中没有提到这一点。 [Access 2010]
ACCESS / VBA如何处理CLASS对象中的错误由可配置选项决定:
VBA代码编辑器&gt;工具&gt;选项&gt;一般&gt;错误捕获:
答案 10 :(得分:2)
我发现以下方法效果最好,称为中央错误处理方法。
您有两种运行应用程序的模式:调试和生产。在 Debug 模式下,代码将在任何意外错误时停止,并允许您通过按两次F8跳转到它出现的行来轻松调试。在 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
要将自己设置为 Debug 模式,请在立即窗口中运行以下命令:
SetDebugMode True
答案 11 :(得分:1)
我个人对此主题前面发表的声明的看法是:
只是为了好玩:
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)