良好的VBAerror handling模式
VBA中的error handling有什么好的模式?
特别是在这种情况下我该怎么做:
... 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) ...
我想要处理这两个错误,并在错误发生后的代码之后恢复执行。 另外,最后的代码必须总是运行 – 不pipe之前抛出什么exception。 我怎样才能达到这个结果?
在VBA中的error handling
-
On Error Goto
ErrorHandlerLabel -
Resume
(Next
| ErrorHandlerLabel ) -
On Error Goto 0
(禁用当前的error handling程序) -
Err
对象
Err
对象的属性通常在error handling例程中重置为零或零长度string,但也可以使用Err.Clear
显式地完成。
error handling例程中的错误正在终止。
范围513-65535可用于用户错误。 对于自定义类错误,您将vbObjectError
添加到错误号。
对于派生类中未实现的接口成员,应该使用常量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
我还要补充:
- 全局的
Err
对象是最接近你的exception对象的 - 用
Err.Raise
可以有效地“抛出exception”
而只是为了好玩:
-
On Error Resume Next
是恶魔化身和被避免,因为它默默地隐藏错误
所以你可以做这样的事情
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来根据需要改变方法的stream程。
这是我的标准实现。 我喜欢标签是自描述性的。
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
专业的Excel开发有一个相当不错的error handlingscheme 。 如果你打算花时间在VBA上,这本书可能是值得的。 VBA缺乏的地方有很多,本书对这些领域的pipe理提出了很好的build议。
PED描述了两种error handling方法。 主要的是一个系统,所有的入口点程序都是子程序,所有其他的程序都是返回布尔值的函数。
入口点过程使用On Error语句来捕获几乎所有的错误。 非入口点程序如果没有错误则返回True,如果有错误则返回False。 非入口点程序也使用On Error。
这两种types的程序都使用中央error handling程序来保持错误状态并logging错误。
这是一个相当不错的模式。
对于debugging:当出现错误时,按下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
我使用了自己开发的一段代码,这对我的代码非常有用:
在函数或子的开始,我定义:
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
我个人对此线程早期发表的观点:
而只是为了好玩:
在错误恢复接下来是恶魔化身和被避免,因为它默默地隐藏错误。
我在程序中使用了On Error Resume Next
,我不想让错误停止我的工作,而且任何语句都不依赖于前面的语句的结果。
当我这样做时,我添加一个全局variablesdebugModeOn
,并将其设置为True
。 然后我用这个方法:
If not debugModeOn Then On Error Resume Next
当我提交我的工作时,我将该variables设置为false,从而将错误隐藏到用户身上,并在testing过程中显示出来。
在做一些可能失败的事情时也使用它,比如调用可能为空的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)
当心大象陷阱:
在这个讨论中我没有看到这一点。 [Access 2010]
ACCESS / VBA如何处理CLASS对象中的错误由一个可configuration的选项决定:
VBA代码编辑器>工具>选项>常规>错误陷印:
下面的代码显示了一个替代scheme,确保子/函数只有一个退出点。
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