Nac*_*rid 1 error-handling vba ms-office
我们都知道 VBA 中只能有一个错误对象。
在帮助同事处理错误以及为什么他不应该使用错误时,On Error Resume Next我有一个想法:
将错误对象存储在某处以便稍后引用它。
考虑这段测试代码:
Sub Test()
Dim t As ErrObject
On Error Resume Next
Err.Raise 1
Set t = Err
On Error GoTo 0
Debug.Print t.Number
On Error Resume Next
Err.Raise 1
Debug.Print t.Number
End Sub
Run Code Online (Sandbox Code Playgroud)
它将在立即窗口中打印 0,因为On Error GoTo 0重置错误对象,然后打印 1,因为它仍然保留对唯一错误对象的引用(?)。
如果我们创建一个新类并为其赋予一些与 ErrObject 相关的属性,如下所示:
(TestClass)
Option Explicit
Public oError As ErrObject
Private Sub Class_Initialize(): End Sub
Private Sub Class_Terminate()
If Not oError Is Nothing Then Set oError = Nothing
End Sub
Public Property Get Error()
Error = oError
End Property
Public Property Set Error(ByVal ErrorObject As ErrObject)
Set oError = ErrorObject
End Property
Run Code Online (Sandbox Code Playgroud)
并像这样创建我们的实例:
Sub Test2()
Dim t As TestClass
On Error Resume Next
Set t = New TestClass
Err.Raise 1
Set t.Error = Err
On Error GoTo 0
Debug.Print t.oError.Number
On Error Resume Next
Err.Raise 1
Debug.Print t.oError.Number
End Sub
Run Code Online (Sandbox Code Playgroud)
我们仍然分别得到 0 和 1 作为输出。
这让我想到了我的问题:当我们无法创建新对象本身但它只是成为指向 VBA 中唯一错误对象的另一个指针时,将变量声明为 ErrObject 有什么用?
没有任何。
Err通常被视为某种全局实例,但事实是,它是一个返回ErrObject1 的函数- 正如对象浏览器中所示:
这个函数的实现方式是这样的,你总是得到相同的对象。
对象需要公开一个接口才能使用,因此函数返回的对象Err公开了该类的ErrObject对象 - 这并不意味着该类ErrObject存在以便可以由用户代码实例化或封装:它只是提供一个接口访问当前运行时错误状态的属性。
当你像你一样封装一个ErrObject时,你本质上只是给自己提供了另一种方式(除了函数之外Err)来访问实例 - 但它仍然是保存当前运行时错误状态ErrObject属性的完全相同的对象。
当对象的属性发生变化时,指向该对象的封装副本将开始报告新值,并且您想要“记住”的旧值将被覆盖。
请注意,这对于任何对象都适用,而不仅仅是ErrObject。
假设我有一个类可以执行您对引用所做的操作ErrObject,但使用Collection:
Private coll As Collection
Public Property Set InternalCollection(ByVal c As Collection)
Set coll = c
End Property
Public Property Get InternalCollection() As Collection
Set InternalCollection = coll
End Property
Run Code Online (Sandbox Code Playgroud)
如果我创建该类的一个实例(我们称之为Class1)并分配c给它InternalCollection,然后将项目添加到c...
Dim c As Collection
Set c = New Collection
With New Class1
Set .InternalCollection = c
c.Add 42
.InternalCollection.Add 42
Debug.Print .InternalCollection.Count
End With
Run Code Online (Sandbox Code Playgroud)
输出是2,因为c和InternalCollection(/封装的coll引用)是同一个对象,这就是封装的ErrObject.
解决方案是不封装其ErrObject本身,而是将其值拉入封装状态的仅获取属性的支持字段中ErrObject:
Private errNumber As Long
Private errDescription As String
'...
Public Sub SetErrorInfo() 'note: an ErrObject argument would be redundant!
With Err
errNumber = .Number
errDescription = .Description
'...
End With
End Sub
Public Property Get Number() As Long
Number = errNumber
End Property
Public Property Get Description() As String
Description = errDescription
End Property
'...
Run Code Online (Sandbox Code Playgroud)
现在,这是否有用还有待争论 - IMO 如果在全局错误状态已经包含相同信息的时刻消耗状态,则无需这样做。
该类可以很容易地被用作 a 的返回类型,Function返回Nothing表示成功,并在失败时封装错误状态 - 问题在于该语言是围绕引发错误而不是返回错误而设计的;在不验证其返回值的情况下“一劳永逸”这样的函数太容易了,并且由于在调用站点,实际的运行时错误状态不会触发On Error语句,因此携带错误状态作为程序数据并不惯用,它创建了一个“令人惊讶”的 API,很容易导致代码最终忽略所有错误。
惯用的错误处理会尽快处理全局运行时错误状态,并在同一范围内恢复,或者让错误状态在调用堆栈中向上冒泡到可以处理的位置。在处理错误之前,ErrObject可以通过全局函数访问状态Err。