VBA 错误“冒泡”

Posted

技术标签:

【中文标题】VBA 错误“冒泡”【英文标题】:VBA Error "Bubble Up" 【发布时间】:2009-09-13 20:33:44 【问题描述】:

我没有阅读太多关于它的内容,但是下面链接中的作者建议我不要使用“冒泡”来集中 VBA 中的错误处理。

Excel Programming Weekend Crash Course via Google Books

但我不知道他为什么推荐这个,他也没有真正解释。

有人能告诉我为什么我应该在每个过程中都进行错误处理而不是使用“冒泡”吗?或者至少,你知道作者为什么不这样做吗?

谢谢。

【问题讨论】:

-1 请不要使用缩短的 URL。没有人喜欢在工作中遵循盲目的链接。 (+1 可通过直接链接获得。) 那个作者完全不正确。 Bubbling 是专业 VBA 编程(作为 25 年 VBA 专业人士)中强大且必不可少的工具。 【参考方案1】:

对您的第一个问题的简短回答是“您不应该在每个过程中都放置一个错误处理程序”。但通常一些例程确实需要它们!

说“每个过程都必须有一个错误处理程序”通常是很糟糕的建议。 VBA 错误处理的缺陷已在别处进行了很多讨论。但是,从概念上讲,它与其他语言中更标准的异常处理形式并没有什么不同。这些语言的大多数最佳实践都适用。您应该在处理它们有意义的最低级别处理错误。有时这是在发生错误的过程中,很多时候不是。

当错误发生时,内部例程通常可以做的最有意义的事情就是让它向上传递堆栈,以便它可以到达知道如何处理它的代码。这实际上取决于例程以及它如何与程序的其余部分相适应。

考虑以下示例:

调用过程中的处理程序将处理它调用的例程引发的所有错误。所以如果一个特定的例程不需要任何清理,那么不要在那里放置任何错误处理代码:

Sub Caller()
          On Error GoTo HANDLER
          ChildProc
          On Error GoTo 0
          
          Exit Sub
HANDLER:
          Debug.Print Error, "Parent cleanup - something happened in either this procedure or a procedure that it called"
End Sub


Sub ChildProc()
          Debug.Print 10 / 0  ' causes error

          'Don't bother handling errors here since there's nothing this routine can do about them
End Sub

另一方面,您可能需要清理任务,在这种情况下,您需要一个错误处理程序。

Sub Caller()
          On Error GoTo HANDLER
          ChildProc
          On Error GoTo 0
          
          Exit Sub
HANDLER:
          Debug.Print Error, "Parent cleanup"
End Sub


Sub ChildProc()
          
          'Pretend this routine gets ahold of some resource that must be cleaned up when it's done
          call get_resources()

          On Error GoTo HANDLER
          Debug.Print 10 / 0  ' causes error
          On Error GoTo 0
          
          'Clean up once we're done
          call release_resources()
          
          Exit Sub
HANDLER:
          Debug.Print Error, "Child cleanup"
          
          'Clean up in case of an error
          call release_resources()

          'Raise another error if necessary to let callers know something went wrong
          Err.Raise 10000, "ChildProc", Error
End Sub

上面的例子只是为了说明为什么在给定的例程中你可能需要或不需要错误处理程序。所以值得注意的是,在实际代码中,“资源”示例通常使用 RAII 技术处理得更好,其中错误处理与资源获取和释放一起封装 - 请参阅 https://***.com/a/3792280/58845 以获取 VBA 示例。并且诸如是否重新引发捕获的错误之类的事情也取决于情况。有时可以完全在本地处理错误,然后无需告诉调用者有任何问题。

您第二个问题的答案是作者似乎不太了解异常处理。他承认错误处理是特定于上下文的,但似乎建议每个过程都应该在“纠正此处的问题并恢复执行”和“终止程序”之间进行本地决定。他遗漏了通常正确的选项,即“在本地清理并将问题踢到楼上”。所以不需要在本地清理的例程应该让错误“冒泡”。

【讨论】:

对于非 VBA 示例,请参阅:***.com/questions/2737328/… 请问有关于“踢楼上”的资源吗? @QHarr,请参阅此答案以及其中包含的链接...***.com/a/4432413/58845 我不同意处理应该只发生在它发生的过程中。调用者和子对象中可能都有清理任务。此外,***过程中的处理程序将方便地处理所有子程序中的所有错误。 @johnywhy,我想我们同意!这就是我回答的重点。 OP 在问为什么那本书建议在每个过程中都进行错误处理。我的观点是这样的建议是不正确的。【参考方案2】:

我的 2 美分: 您应该在所有公共过程和事件上放置错误处理程序。这意味着调用堆栈底部的过程将始终有一个错误处理程序。然后在您的其他过程中添加错误处理程序,因为它是有意义的。如果在没有错误处理程序的过程中发生错误,它将“冒泡”到***错误处理程序,在那里以专业的方式记录/显示。 您可能希望将错误处理程序添加到私有(较低级别)过程的场景是: 代码需要快速。您有一个可以避免的罕见情况,但会迫使您在循环内执行昂贵的逻辑测试(或更糟糕的是嵌套循环)。 您可以在错误处理程序中执行逻辑测试,如果说“很少发生”,请进行更正并恢复。由于这种情况很少见,您将看到大多数情况下的性能提升。如果错误处理程序无法找出并纠正问题,则重新引发错误以将其冒泡到堆栈中。

显然这只是一种情况。

【讨论】:

【参考方案3】:

我不确定 VBA 的默认错误处理是什么,但由于它的 Visual Basic for Applications 以及这些应用程序包括 excel 和 word 之类的东西,我假设只会出现一个对话框,这对用户。

我假设作者已经被代码不处理错误所困扰,所以他现在推荐所有程序来处理错误。

完整的答案是,您必须了解可能发生的每个错误,并准备好代码来处理它,无论是尽可能低(您可能不知道该怎么做),还是尽可能高尽可能地(这意味着编写错误处理代码的工作量更少,但不知道错误发生的原因),或策略性地(恰好在您应该能够从最常见错误中恢复的正确位置)或无处不在(可能是只是太多的开发工作)。

【讨论】:

VBA 确实 允许错误冒泡。有例外,但一般来说,您不必在每个方法中都进行错误处理,以防止应用程序出现不和谐的弹出窗口。【参考方案4】:

我在他的解释中看到了至少一个原因:因为这样做会剥夺您对 Resume 的好处(下一个)。 另外,您将不知道错误发生在哪个模块中。

【讨论】:

【参考方案5】:

最好不要在错误处理中使用“冒泡”部分,因为应该处理错误并且如果知道要做什么,如果发生这样的错误 - 程序更清楚调用过程要做什么

Sub test()
  On Error GoTo e
  Dim c As Integer
  Dim d As Integer
  c = add(5, 0)
  d = divideWhichManagedItsOwnErrorHandling(5, 0)
  d = divide(5, 0)

  Exit Sub

e:
  MsgBox "error occurred somewhere for which I don't know what to do: " + Err.Description
End Sub

Function add(a As Integer, b As Integer) As Integer
   add = a + b
End Function

Function divide(a As Integer, b As Integer) As Integer
   divide = a / b 'if error occurs, it will "bubble-up" to the caller.
End Function

Function divideWhichManagedItsOwnErrorHandling(a As Integer, b As Integer) As Integer
  On Error Resume Next
  Dim result As Integer
  result = a / b
  If Err.Number = 11 Then 'if divide by zero occurred, user must have passed 0 for b
    result = 0 ' return 0 if the divide by zero occurs. 
  End If
  divideWhichManagedItsOwnErrorHandling = result
End Function

【讨论】:

a/0 不是 0!你只是吞下了一个真正的错误并返回了一个错误的结果。 @jtolle:我不是在这里讨论除法的结果。这只是为了给出一个错误处理的例子,特别是在错误发生的地方处理。 那么你需要一个更好的例子。不应以这种方式处理该特定错误。

以上是关于VBA 错误“冒泡”的主要内容,如果未能解决你的问题,请参考以下文章

关于vba代码错误"91"?

excel vba 运行时错误

VBA编程常用排序算法冒泡排序

VBA 错误:“编译错误:预期结束子”

在 VBA 中处理错误时如何管理无错误情况? [复制]

接下来没有错误VBA