Какая польза от объявления переменной ErrObject, если когда-либо может существовать только один объект ошибки? - PullRequest
1 голос
/ 08 марта 2019

Мы все знаем, что в 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

Он выведет 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

И создайте наш экземпляр следующим образом:

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

Мы по-прежнему получаем 0 и 1 в качестве вывода соответственно.

Это подводит меня к моему вопросу: Какая польза от объявления переменной как ErrObject, когда мы не можем создать новый объект сам, но он просто становится другим указателем на единственный объект ошибки в VBA?

1 Ответ

4 голосов
/ 08 марта 2019

Ничего.

Err часто рассматривается как некий глобальный ErrObject экземпляр, но на самом деле это функция, которая возвращает единицу, как показано в обозревателе объектов:

enter image description here

И эта функция реализована таким образом, что вы всегда получаете один и тот же объект.

Объекты должны предоставлять доступный для использования интерфейс, поэтому объект, возвращаемый функцией 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

Если я сделаю экземпляр этого класса (назовем его 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

Выходные данные 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

'...

Теперь, полезно ли это обсуждать - IMO, если состояние используется в момент, когда состояние глобальной ошибки уже содержит ту же информацию, нет необходимости делать это.

Класс можно довольно легко использовать [ab] в качестве возвращаемого типа для Function, который возвращает Nothing для обозначения успеха, и инкапсулированного состояния ошибки в случае сбоя - проблема в том, что язык разработан на основе поднимает ошибок, а не возвращает их; слишком легко «запустить и забыть» такую ​​функцию, не проверяя ее возвращаемое значение, и, поскольку на сайте вызова фактическое состояние ошибки времени выполнения не собирается отключать оператор On Error, несущий состояние ошибки: данные программы не идиоматичны, они создают «удивительный» API, который может легко привести к тому, что код будет игнорировать все ошибки.

Идиоматическая обработка ошибок обрабатывает глобальное состояние ошибки времени выполнения как можно скорее и либо восстанавливает в той же области, либо позволяет состоянию ошибки пузыриться в стеке вызовов там, где его можно обработать. И пока ошибка не будет обработана, состояние ErrObject доступно через глобальную функцию Err.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...