VBA错误“冒泡上浮”

8
我没仔细研究过这个问题,但下面链接的作者建议我不要在VBA中使用“冒泡”来集中处理错误。 通过谷歌图书查看Excel编程周末速成课 但我不确定他为什么这样建议,他也没有解释原因。
有人能告诉我为什么我应该在每个过程中都放置错误处理,而不是使用“冒泡”吗?或者至少,你知道作者为什么不建议使用吗?
谢谢。

2
请不要使用缩短的URL链接。在工作中,没有人喜欢跟随盲目的链接。(如果提供直接链接,则可获得+1) - Oorang
1
那个作者完全是错误的。冒泡排序在专业的VBA编程中是一种强大且必不可少的工具(我是一名有25年经验的VBA专家)。 - johny why
5个回答

17
你第一个问题的简短回答是“不应该在每个过程中都放置错误处理程序”。但通常有些例程确实需要它们!
说“每个过程都必须有错误处理程序”通常是可怕的建议。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://dev59.com/P2865IYBdhLWcg3wfemU#3792280 中的VBA示例。而是否重新引发已捕获的错误等问题也具有情境依赖性。有时可以完全本地处理错误,然后无需告知调用者任何错误发生。
关于您的第二个问题的答案是,作者似乎并不太了解异常处理。他承认错误处理是上下文相关的,但随后似乎建议每个过程应在“纠正问题并继续执行”和“终止程序”之间进行本地决策。他省略了通常正确的选项,即“本地清理并将问题提升”。因此,没有本地清理需求的例程应该只是让错误“冒泡”。

1
请提供关于"踢球上楼"的任何资源。 - QHarr
1
@QHarr,请查看此答案以及其中包含的链接...https://dev59.com/UFLTa4cB1Zd3GeqPcJ9J#4432413 - jtolle
我不同意处理应该仅在发生的过程中进行。调用者和子程序中可能都有清理任务。此外,顶层过程中的处理程序将方便地处理所有子程序中的所有错误。 - johny why
1
@johnywhy,我认为我们意见一致!这就是我的回答的重点。OP问为什么那本书推荐在每个过程中都放置错误处理,而我的观点是这样的建议是不正确的。 - jtolle
@jtolle,您的原始文本不太清楚:“您不应该在每个过程中都放置错误处理程序”。我进行了编辑以使其更加清晰,并添加了示例。 - johny why
显示剩余5条评论

3

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

显然,这只是一个场景。


1
我在他的解释中至少看到一个原因:这样做会剥夺你获取简历(下一步)的好处。
此外,你也无法知道错误发生在哪个模块。

1

我不确定VBA的默认错误处理方式是什么,但由于它是Visual Basic for Applications,而这些应用程序包括像Excel和Word这样的东西,我认为只会出现一个对用户没有帮助的对话框。

我猜作者曾经因为代码没有处理错误而受到过打击,所以现在建议所有过程都要处理错误。

完整的答案是,您必须意识到可能发生的每个错误,并编写代码来处理它,无论它是尽可能低(您可能不知道该怎么做),还是尽可能高(这意味着编写错误处理代码的工作量较小,但不知道错误发生的原因),或者是战略性的(就是在应该能够从大多数常见错误中恢复的正确位置)或者是无处不在的(这可能只是太多的开发工作)。


VBA确实允许错误上浮。虽然有例外情况,但通常情况下,您不必在每个方法中都添加错误处理程序来防止应用程序出现突兀的弹出窗口。 - Charles Wood

0

最好不要使用错误处理的“冒泡”部分,因为错误应该被处理,如果已知该怎么做,当这样的错误发生时,程序应该知道该怎么做,而不是调用过程

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

1
a/0不是0!你刚刚忽略了一个真正的错误并返回了错误的结果。 - jtolle
1
@jtolle:我这里不讨论除法的结果。这只是为了举例说明错误处理,特别是在出现错误时如何处理。 - shahkalpesh
你需要一个更好的例子。那个特定的错误不应该以那种方式处理。 - jtolle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接