你能帮助我了解更多关于 VBA 错误处理的良好做法吗?
Can you help me understand more about good practise with VBA Error handling please?
过去两天我一直在努力更好地理解 VBA 错误处理,但仍然对实际发生的事情有疑问。下面的文字描述了我所做的一些认识,并在描述中嵌入了一些问题。我真的很想找人帮助我完善我的理解并给我一些指导。我掌握了常用的基础知识,我正在尝试更多地关注细微的功能。
期待您的任何答复!
重要的是要意识到 VBA 中发生错误时会发生两种截然不同的事情。
错误对象已实例化并设置了属性(即 err.number、err.desciption、err.source 等)
要执行的下一行发生变化。
接下来执行哪一行由执行的最后一个 "On Error Goto" 语句决定 - 如果有的话。
这些是独立但高度相关的主题,您将编写有效的、截然不同但交织在一起的代码来管理它们。
当发生任何错误或您使用 Err.Raise 时,Err 对象总是会被设置。即使使用了 "On Error Resume next" 或任何其他 On 错误语句。
所以像这样的代码总是可以使用的:
Dim i as integer
On error resume next
i = 100/0 ' raises error
if err.number <> 0 then
' respond to the error
end if
意识到当错误对象对 err.number 有一个非零值时,异常已经被抛出并且如果你随后尝试执行任何 "On Error Goto " 语句这样做将是非常重要的本身引发错误,执行将传递给调用当前过程的任何代码。 (或者在程序未被任何代码调用的地方给出通常的 VBA 错误对话框)。因此,在下面的示例场景中,"On Error Goto ALabel1" 不会将下一行更改为带有 Label1: 的行。
例如
Sub ErrorTest()
Dim dblValue As Double
On Error GoTo ErrHandler1
dblValue = 1 / 0
ErrHandler1:
debug.print "Exception Caught"
debug.print Err.Number
On Error GoTo ALabel1
dblValue = 1 / 0 '' THIS LINE ACTUALLY WILL RAISE AN unhandled ERROR!
Exit sub
ALabel1:
debug.print "Again caught it."
End Sub
一旦 err.number 属性 设置为非零,您可以使用
将其重置为零
On Error Goto -1
请注意 Err.Clear 也将其重置为零,但实际上等同于:
On Error Goto -1
On Error Goto 0
即 Err.Clear 删除当前存在的 "On Error Goto"。因此,最好使用:
On Error Goto -1
与使用 Err.clear 一样,您通常需要添加额外的一行来重新设置已存在的错误处理程序或其他错误处理程序。
Err.Clear
On Error Goto MyErrorHandlerLabel
"Alternatively, you can set the error number to zero (Err.Number = 0), but is not as effective as the Clear method since it does not clear the description property."(来自 MSDN 页面)
我在 MSDN 上阅读了下一段我认为但不明白错误对象是如何被实例化的(即 err.number <> 0),它的值被传递给任何执行 End Sub 时的调用过程。
Q1:求助!
"It is worth noting that Err.Clear is implicitly carried out by VBA whenever it executes any type of Resume statement, Exit Sub, Exit Function, Exit Property, or any On Error statement."(来自 MSDN 页面)
您还可以将错误对象设置为您喜欢的任何数字
Err.Raise 编号:=, 来源:=, 描述:=
Err.Raise 非常重要,因为它允许您将错误传播到调用程序并无限地一直 "up" 到与用户打交道的顶级程序。您还可以提出自己的错误编号,称为 "user defined errors"。这提供了一种方法来告诉调用程序它由于某种原因无法继续(即发生意外错误或业务规则被破坏)。
通过这种方式,调用过程可以决定如何处理错误,而不是发生错误的过程(它不了解用户正在做什么,而最重要的是调用次数最多的程序!)
问题 2:我对此是否正确?
Q3:您会为此提出用户定义的异常吗?
您可以使用像
这样的语句来控制接下来执行哪一行代码
On Error Goto ALabelName
On Error Goto ANonZeroLineNumber
和
On Error Goto 0
On Error Goto 0 是一种特殊情况,因为它实际上表示“在当前范围内(通常是子或函数),如果发生错误,则将错误对象传递回调用当前代码的代码子或函数,不要在当前子或函数中执行任何更多代码。
在 VBA 中使用简单的错误处理非常简单,但当事情变得更加棘手时,我常常发现自己陷入困境。
没有 SQL Server 和 VBA 常用的 TRY CATCH 语句是一种耻辱,所以我现在在这里成功地模拟了这一点:
如果您有兴趣,我希望以上 post 能让您更深入地了解我一直在做的实验,并帮助您理解我为什么会遇到困难。
简而言之,尽管有一些有用的网站,例如:
http://www.cpearson.com/excel/errorhandling.htm
但是,我觉得我可以做更多的指导。
哈维
共有三种错误模式。
On Error Goto 0
默认值。没有错误处理。 VBA 崩溃并修复所有基本信息错误。
On Error Resume Next
。您正在处理所有错误。在可能产生错误的任何行之后(但不是每一行 - msgbox 将始终有效)你做 If err.number <> 0 then FixErrorOrCrash
。任何涉及文件、注册表项、网络或 Internet 的东西都不能指望在那一刻存在或正在工作。同样是非系统对象,例如 Office。
On Error Goto Label
。你也在处理所有的错误,除非你不知道错误在哪里。这用于撤消功能。如果有任何失败,这让您有机会回滚函数可能完成的所有操作。
此外,在编程时,执行并捕获错误比测试然后执行更有效。与第一种磁盘访问方式相比,第二种方式访问文件需要两次访问才能成功。
这个答案是关于你的问题中似乎缺少的错误处理程序。
You said "two distinct things that happen when an error occurs in VBA."
"The error object is instanciated and has it's properties set (ie err.number, err.desciption, err.source etc)"
"The next line to be executed changes. Which line is executed next is determined by the last "On Error Goto" statement that was executed - if any."
第一项正确。第二个离基地很远;你完全错过了错误处理程序的概念。错误处理程序是 On Error Goto <label>
语句的 "enabled"。然后,当发生错误时,错误处理程序变为 "active".
最好将活动错误处理程序视为一种条件。当错误处理程序处于活动状态时触发错误时,您的代码会进入这种特殊情况。错误对象也设置了属性。
错误处理代码通常位于过程的底部。参见 Explanation of regular error handling 这避免了错误处理块和错误处理条件之间的混淆。我建议你回到这个,因为它很容易解释和重现。 Try/Catch 区块的尝试让几乎所有人都感到困惑。
您无法通过跳转到代码中的其他位置来退出错误处理条件。必须避免在没有 Resume ...
语句的情况下这样做。
退出错误处理条件的唯一方法是:
Resume
Resume Next
Resume <label>
- 退出程序
On error goto -1
' 使用传统的错误处理可以避免这种情况,错误处理代码位于底部。
Chip Pearson: Using On Error Goto -1 can be a very dangerous thing to
do, and I would avoid it at all costs.
错误处理块的概念并不完全等同于错误处理条件。代码块在视觉上是代码中的一个区域。 VBA 对此毫不关心。 VBA 只知道错误处理条件。同时使用 resume 会使您退出错误处理代码 block 并取消错误处理 condition。它还会重置错误对象。
你问关于好的练习,这是我诚实的回答:
真正好的做法是尽可能少使用VBA。
如果您打算创建一个更大的项目,那么我建议在 C# 中创建一个模块,并在 C# 中调用 office 函数。
然后将您的代码用作加载项或函数库。换句话说:仅使用VBA来调用模块的主要入口函数。但是在 C# 中实现主要功能。
我们生活在 2015 年。有了 C# 和 Visual Studio,您现在拥有了强大的工具。但是 VBA 是在 90 年代引入的。语言和 IDE 是有限的。对于许多事情,例如正确的错误处理,您需要糟糕的解决方法。
顺便说一句,VBA 语言甚至在它诞生的时候就已经过时了。在 90 年代,我们已经有了更好的面向对象概念。遗憾的是,微软一直没有为他们的办公产品创造更好的语言。为了成为 "backward compatible" 并使用户的转换最容易,他们牺牲了一种更好的语言。恕我直言,这是一个错误的决定。因此,今天我们生活在世界范围内以有限且蹩脚的语言编写的数百万个项目中。
我已经停止将例程声明为 "sub" 过程,而是我总是使用 "functions"(称为 "method" 的子程序除外,即顶级过程)。
这些函数 return true(如果有错误则为 false),因为这样做可以绕过 VBA 的限制。可以使用其他 return 类型,但关键是要有一个或多个 return 值表示发生了错误。
我使用 return 值向上传播错误,因为尝试重新引发错误在 VBA 中是有问题的。这种方法有效,而且通常使用起来非常简单。
我仍然对人们的意见感兴趣。 (关于 VBA 中的错误处理,我得到的最佳答案是使用 C# 并尽可能避免使用 VBA!)
Public Function AFunction1(ID As Long) As Boolean
Dim Qdf as querydef
On Error GoTo ErrHandler
set Qdf = .etc....
' Note Afunction2 is declared with the same structure as AFunction1
If not AFunction2 (ID2 as Long)_
Then
GoTo ErrHandlerMsgAlreadyGiven
End If
' True is only returned if the code gets to here
AFunction1 = True
GoTo CleanUp
ErrHandler:
MsgBox "AFunction1 Blargh Blargh Error: " & Err.Number & " " & Err.Description
AFunction1 = False ' this line is not really needed but re-inforces what is happening
Resume CleanUp
Resume 'debugging only
ErrHandlerMsgAlreadyGiven:
AFunction1 = False ' this line is not really needed but re-inforces what is happening
GoTo CleanUp
CleanUp:
Set Qdf = Nothing
Exit Function ' this line is not really needed but re-inforces what is happening
End Function
过去两天我一直在努力更好地理解 VBA 错误处理,但仍然对实际发生的事情有疑问。下面的文字描述了我所做的一些认识,并在描述中嵌入了一些问题。我真的很想找人帮助我完善我的理解并给我一些指导。我掌握了常用的基础知识,我正在尝试更多地关注细微的功能。
期待您的任何答复!
重要的是要意识到 VBA 中发生错误时会发生两种截然不同的事情。
错误对象已实例化并设置了属性(即 err.number、err.desciption、err.source 等)
要执行的下一行发生变化。
接下来执行哪一行由执行的最后一个 "On Error Goto" 语句决定 - 如果有的话。
这些是独立但高度相关的主题,您将编写有效的、截然不同但交织在一起的代码来管理它们。
当发生任何错误或您使用 Err.Raise 时,Err 对象总是会被设置。即使使用了 "On Error Resume next" 或任何其他 On 错误语句。
所以像这样的代码总是可以使用的:
Dim i as integer
On error resume next
i = 100/0 ' raises error
if err.number <> 0 then
' respond to the error
end if
意识到当错误对象对 err.number 有一个非零值时,异常已经被抛出并且如果你随后尝试执行任何 "On Error Goto " 语句这样做将是非常重要的本身引发错误,执行将传递给调用当前过程的任何代码。 (或者在程序未被任何代码调用的地方给出通常的 VBA 错误对话框)。因此,在下面的示例场景中,"On Error Goto ALabel1" 不会将下一行更改为带有 Label1: 的行。
例如
Sub ErrorTest()
Dim dblValue As Double
On Error GoTo ErrHandler1
dblValue = 1 / 0
ErrHandler1:
debug.print "Exception Caught"
debug.print Err.Number
On Error GoTo ALabel1
dblValue = 1 / 0 '' THIS LINE ACTUALLY WILL RAISE AN unhandled ERROR!
Exit sub
ALabel1:
debug.print "Again caught it."
End Sub
一旦 err.number 属性 设置为非零,您可以使用
将其重置为零On Error Goto -1
请注意 Err.Clear 也将其重置为零,但实际上等同于:
On Error Goto -1
On Error Goto 0
即 Err.Clear 删除当前存在的 "On Error Goto"。因此,最好使用:
On Error Goto -1
与使用 Err.clear 一样,您通常需要添加额外的一行来重新设置已存在的错误处理程序或其他错误处理程序。
Err.Clear
On Error Goto MyErrorHandlerLabel
"Alternatively, you can set the error number to zero (Err.Number = 0), but is not as effective as the Clear method since it does not clear the description property."(来自 MSDN 页面)
我在 MSDN 上阅读了下一段我认为但不明白错误对象是如何被实例化的(即 err.number <> 0),它的值被传递给任何执行 End Sub 时的调用过程。
Q1:求助!
"It is worth noting that Err.Clear is implicitly carried out by VBA whenever it executes any type of Resume statement, Exit Sub, Exit Function, Exit Property, or any On Error statement."(来自 MSDN 页面)
您还可以将错误对象设置为您喜欢的任何数字
Err.Raise 编号:=, 来源:=, 描述:=
Err.Raise 非常重要,因为它允许您将错误传播到调用程序并无限地一直 "up" 到与用户打交道的顶级程序。您还可以提出自己的错误编号,称为 "user defined errors"。这提供了一种方法来告诉调用程序它由于某种原因无法继续(即发生意外错误或业务规则被破坏)。
通过这种方式,调用过程可以决定如何处理错误,而不是发生错误的过程(它不了解用户正在做什么,而最重要的是调用次数最多的程序!)
问题 2:我对此是否正确?
Q3:您会为此提出用户定义的异常吗?
您可以使用像
这样的语句来控制接下来执行哪一行代码On Error Goto ALabelName
On Error Goto ANonZeroLineNumber
和
On Error Goto 0
On Error Goto 0 是一种特殊情况,因为它实际上表示“在当前范围内(通常是子或函数),如果发生错误,则将错误对象传递回调用当前代码的代码子或函数,不要在当前子或函数中执行任何更多代码。
在 VBA 中使用简单的错误处理非常简单,但当事情变得更加棘手时,我常常发现自己陷入困境。
没有 SQL Server 和 VBA 常用的 TRY CATCH 语句是一种耻辱,所以我现在在这里成功地模拟了这一点:
如果您有兴趣,我希望以上 post 能让您更深入地了解我一直在做的实验,并帮助您理解我为什么会遇到困难。
简而言之,尽管有一些有用的网站,例如:
http://www.cpearson.com/excel/errorhandling.htm
但是,我觉得我可以做更多的指导。
哈维
共有三种错误模式。
On Error Goto 0
默认值。没有错误处理。 VBA 崩溃并修复所有基本信息错误。On Error Resume Next
。您正在处理所有错误。在可能产生错误的任何行之后(但不是每一行 - msgbox 将始终有效)你做If err.number <> 0 then FixErrorOrCrash
。任何涉及文件、注册表项、网络或 Internet 的东西都不能指望在那一刻存在或正在工作。同样是非系统对象,例如 Office。On Error Goto Label
。你也在处理所有的错误,除非你不知道错误在哪里。这用于撤消功能。如果有任何失败,这让您有机会回滚函数可能完成的所有操作。
此外,在编程时,执行并捕获错误比测试然后执行更有效。与第一种磁盘访问方式相比,第二种方式访问文件需要两次访问才能成功。
这个答案是关于你的问题中似乎缺少的错误处理程序。
You said "two distinct things that happen when an error occurs in VBA."
"The error object is instanciated and has it's properties set (ie err.number, err.desciption, err.source etc)"
"The next line to be executed changes. Which line is executed next is determined by the last "On Error Goto" statement that was executed - if any."
第一项正确。第二个离基地很远;你完全错过了错误处理程序的概念。错误处理程序是 On Error Goto <label>
语句的 "enabled"。然后,当发生错误时,错误处理程序变为 "active".
最好将活动错误处理程序视为一种条件。当错误处理程序处于活动状态时触发错误时,您的代码会进入这种特殊情况。错误对象也设置了属性。
错误处理代码通常位于过程的底部。参见 Explanation of regular error handling 这避免了错误处理块和错误处理条件之间的混淆。我建议你回到这个,因为它很容易解释和重现。 Try/Catch 区块的尝试让几乎所有人都感到困惑。
您无法通过跳转到代码中的其他位置来退出错误处理条件。必须避免在没有 Resume ...
语句的情况下这样做。
退出错误处理条件的唯一方法是:
Resume
Resume Next
Resume <label>
- 退出程序
On error goto -1
' 使用传统的错误处理可以避免这种情况,错误处理代码位于底部。
Chip Pearson: Using On Error Goto -1 can be a very dangerous thing to do, and I would avoid it at all costs.
错误处理块的概念并不完全等同于错误处理条件。代码块在视觉上是代码中的一个区域。 VBA 对此毫不关心。 VBA 只知道错误处理条件。同时使用 resume 会使您退出错误处理代码 block 并取消错误处理 condition。它还会重置错误对象。
你问关于好的练习,这是我诚实的回答:
真正好的做法是尽可能少使用VBA。
如果您打算创建一个更大的项目,那么我建议在 C# 中创建一个模块,并在 C# 中调用 office 函数。
然后将您的代码用作加载项或函数库。换句话说:仅使用VBA来调用模块的主要入口函数。但是在 C# 中实现主要功能。
我们生活在 2015 年。有了 C# 和 Visual Studio,您现在拥有了强大的工具。但是 VBA 是在 90 年代引入的。语言和 IDE 是有限的。对于许多事情,例如正确的错误处理,您需要糟糕的解决方法。
顺便说一句,VBA 语言甚至在它诞生的时候就已经过时了。在 90 年代,我们已经有了更好的面向对象概念。遗憾的是,微软一直没有为他们的办公产品创造更好的语言。为了成为 "backward compatible" 并使用户的转换最容易,他们牺牲了一种更好的语言。恕我直言,这是一个错误的决定。因此,今天我们生活在世界范围内以有限且蹩脚的语言编写的数百万个项目中。
我已经停止将例程声明为 "sub" 过程,而是我总是使用 "functions"(称为 "method" 的子程序除外,即顶级过程)。
这些函数 return true(如果有错误则为 false),因为这样做可以绕过 VBA 的限制。可以使用其他 return 类型,但关键是要有一个或多个 return 值表示发生了错误。
我使用 return 值向上传播错误,因为尝试重新引发错误在 VBA 中是有问题的。这种方法有效,而且通常使用起来非常简单。
我仍然对人们的意见感兴趣。 (关于 VBA 中的错误处理,我得到的最佳答案是使用 C# 并尽可能避免使用 VBA!)
Public Function AFunction1(ID As Long) As Boolean
Dim Qdf as querydef
On Error GoTo ErrHandler
set Qdf = .etc....
' Note Afunction2 is declared with the same structure as AFunction1
If not AFunction2 (ID2 as Long)_
Then
GoTo ErrHandlerMsgAlreadyGiven
End If
' True is only returned if the code gets to here
AFunction1 = True
GoTo CleanUp
ErrHandler:
MsgBox "AFunction1 Blargh Blargh Error: " & Err.Number & " " & Err.Description
AFunction1 = False ' this line is not really needed but re-inforces what is happening
Resume CleanUp
Resume 'debugging only
ErrHandlerMsgAlreadyGiven:
AFunction1 = False ' this line is not really needed but re-inforces what is happening
GoTo CleanUp
CleanUp:
Set Qdf = Nothing
Exit Function ' this line is not really needed but re-inforces what is happening
End Function