【问题标题】:vba nested error handlingvba 嵌套错误处理
【发布时间】:2017-08-24 19:04:39
【问题描述】:

我正在尝试了解如何为下面引用的情况设置错误处理。 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

【问题讨论】:

  • 通常你让每个错误处理程序在它自己的 Function 或 Sub 中处理错误。但是,在“A”依赖于“B”的情况下,我会将“B”设为函数,以便将“通过/失败”状态返回给“A”。如果你的对象没有打开,你可能会在“B”中遇到问题,在那里你分支到“清理”(即如果 RS 没有打开,那么错误,你将进入一个循环,因为它会继续调用错误处理程序)。是的,我意识到这只是示例代码...
  • 似乎只有 ErrorHandler A 始终处于活动状态你到底是什么意思?如果你在ErrorHandlerB 下的GoTo cleanUp 指令上中断,当B 出现错误时,断点不会被命中吗?请edit您的问题澄清您所期望的行为。

标签: vba error-handling


【解决方案1】:
Exit Sub
ErrorHandlerA:
...
Resume
End Sub

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

cleanUp:
con.close
rs.close

Exit Sub
ErrorHandlerB:
GoTo cleanup

End Sub

这里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

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

Inside DoSomething[A]: 5
Inside DoSomething[B]: 0
After DoSomething: 0

Resume B 指令更改为GoTo B 会产生以下输出:

Inside DoSomething[A]: 5
Inside DoSomething[B]: 5
After DoSomething: 0

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

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

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

    Inside Test[A]: 5
    

    当调用代码知道最佳行动方案是什么时,您通常会这样做(例如,向用户显示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
    

    这个输出:

    Inside DoSomething[A]: 5
    Inside DoSomething[B]: 5
    Inside Test[A]: 5
    

根据您希望在两个不同位置处理相同错误的确切原因,您还可以考虑将B 设为Function,它返回一个指示成功或失败的Boolean - 在这种情况下为@987654354 @ 本身不再处理错误,而是使用正常的流控制来确定要做什么:

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

产生这个输出:

Inside DoSomething[A]: 5
Inside DoSomething[B]: 5
After DoSomething failed: 0

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-10
    • 2013-03-26
    • 1970-01-01
    相关资源
    最近更新 更多