VBA嵌套错误处理

For*_*mer 2 error-handling vba

我试图了解如何针对以下情况设置错误处理。Sub A具有针对其自身上下文的错误处理。但是,它调用Sub B,后者创建数据库连接,结果集等,并在其错误处理程序中对其进行清理。这样看来,似乎只有ErrorHandler A一直处于活动状态。如何在调用B时激活ErrorHandler B并在B返回后恢复为错误处理。

谢谢

Sub A
' Preps for database access
On Error GoTo ErrorHandlerA

B
.
.

Exit Sub
ErrorHandlerA:
...
Resume
End Sub

Sub B
' Does database access stuff

On Error ErrorHandlerB

cleanUp:
con.close
rs.close

Exit Sub
ErrorHandlerB:
GoTo cleanup

End Sub
Run Code Online (Sandbox Code Playgroud)

Mat*_*don 5

Exit Sub
ErrorHandlerA:
...
Resume
End Sub
Run Code Online (Sandbox Code Playgroud)

Resume在此,恢复正常执行(清除“错误处理模式”)并跳回到导致错误的指令。大概(希望如此)...成功进行了先前失败的调用的包含代码。

cleanUp:
con.close
rs.close

Exit Sub
ErrorHandlerB:
GoTo cleanup

End Sub
Run Code Online (Sandbox Code Playgroud)

在这里,您正在GoTo跳到错误处理子例程之外,而没有恢复到正常执行,因此cleanUp行标签下的指令在错误处理上下文中执行。那应该是Resume cleanUp,不是GoTo

Exit Sub语句将离开作用域,该作用域将清除错误状态并隐式恢复正常执行。这是一个小MCVE

Public Sub Test()
On Error GoTo A
    DoSomething
    Debug.Print "After DoSomething: " & Err.Number
    Exit Sub
A:
    Debug.Print "Inside Test[A]: " & Err.Number
End Sub

Private Sub DoSomething()
On Error GoTo A

    Err.Raise 5

B:
    Debug.Print "Inside DoSomething[B]: " & Err.Number
    Exit Sub
A:
    Debug.Print "Inside DoSomething[A]: " & Err.Number
    Resume B
End Sub
Run Code Online (Sandbox Code Playgroud)

运行该Test过程将产生以下输出:

Exit Sub
ErrorHandlerA:
...
Resume
End Sub
Run Code Online (Sandbox Code Playgroud)

更改a的Resume B指令将GoTo B产生以下输出:

cleanUp:
con.close
rs.close

Exit Sub
ErrorHandlerB:
GoTo cleanup

End Sub
Run Code Online (Sandbox Code Playgroud)

如您所见,由于在中处理了DoSomething错误,执行返回到时,错误状态将重置Test,并且过程中的处理Test程序永远不会触发。

如果要将错误传播给调用者,则有多种选择:

  • 不要处理被调用过程中的错误。在上面的示例中,这意味着DoSomething没有On Error声明;任何运行时错误都将使调用堆栈“冒泡”,并且输出现在看起来像这样:

    Inside Test[A]: 5
    
    Run Code Online (Sandbox Code Playgroud)

    当调用代码知道最佳操作方法是什么(例如MsgBox,向用户显示错误或记录错误等)时,通常会执行此操作。

  • 处理错误,然后重新提出。在您的示例中,这意味着将清除代码移动(或复制)到ErrorHandlerB子例程中,然后调用Err.Raise Err.Number而不是Resume。或者,您可以保留GoTo,然后清理子例程可以If Err.Number <> 0 Then Err.Raise Err.Number有效地将错误重新引发给调用者以进行处理。换一种说法:

    Public Sub Test()
    On Error GoTo A
        DoSomething
        Debug.Print "After DoSomething: " & Err.Number
        Exit Sub
    A:
        Debug.Print "Inside Test[A]: " & Err.Number
    End Sub
    
    Private Sub DoSomething()
    On Error GoTo A
    
        Err.Raise 5
    
    B:
        Debug.Print "Inside DoSomething[B]: " & Err.Number
        If Err.Number <> 0 Then Err.Raise Err.Number
        Exit Sub
    A:
        Debug.Print "Inside DoSomething[A]: " & Err.Number
        GoTo B 'resuming would clear the error and prevent rethrow
    End Sub
    
    Run Code Online (Sandbox Code Playgroud)

    输出:

    Public Sub Test()
    On Error GoTo A
        DoSomething
        Debug.Print "After DoSomething: " & Err.Number
        Exit Sub
    A:
        Debug.Print "Inside Test[A]: " & Err.Number
    End Sub
    
    Private Sub DoSomething()
    On Error GoTo A
    
        Err.Raise 5
    
    B:
        Debug.Print "Inside DoSomething[B]: " & Err.Number
        Exit Sub
    A:
        Debug.Print "Inside DoSomething[A]: " & Err.Number
        Resume B
    End Sub
    
    Run Code Online (Sandbox Code Playgroud)

根据您要在两个不同地方处理同一错误的确切原因,您还可以考虑使Ba Function返回Boolean成功或失败的指示-在这种情况下,A不再处理错误本身,而是使用常规流控制确定该怎么做:

Public Sub Test()
On Error GoTo A
    If Not DoSomething Then
        Debug.Print "After DoSomething failed: " & Err.Number
        Exit Sub
    End If
    Exit Sub
A:
    Debug.Print "Inside Test[A]: " & Err.Number
End Sub

Private Function DoSomething() As Boolean
'Dim success As Boolean
On Error GoTo A

    Err.Raise 5
    'success = True

B:
    Debug.Print "Inside DoSomething[B]: " & Err.Number
    DoSomething = (Err.Number = 0) 'DoSomething = success
    Exit Function
A:
    Debug.Print "Inside DoSomething[A]: " & Err.Number
    'success = False
    GoTo B 'Resume B
End Function
Run Code Online (Sandbox Code Playgroud)

产生以下输出:

Inside DoSomething[A]: 5
Inside DoSomething[B]: 0
After DoSomething: 0
Run Code Online (Sandbox Code Playgroud)

注意,Test已知DoSomething失败,但Err.Number为0。通常,这取决于将运行时错误用于流控制,这再次取决于您的实际情况。还要注意,GoTo在这种情况下,使用简单的布尔局部变量来跟踪您的返回值(注释掉的代码)是可以避免的。