Шаблон для обработки ожидаемых ошибок локально, повторные неожиданные ошибки

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

В примере (слегка надуманном) ниже функция FindInArray может вызывать различные типы ошибок. Один из них, ERR__ELEMENT_NOT_FOUND_IN_ARRAY, более или менее ожидаемый, и поэтому я хочу обрабатывать его локально. Но могут также встречаться другие номера ошибок, и если да, то я хочу, чтобы с ними обрабатывалась процедура обработки ошибок.

Я нахожу, что если я столкнулся с некоторыми ожидаемыми номерами ошибок локально, я не могу легко "перебросить" неожиданные номера ошибок, которые будут рассмотрены в другом месте.

Как отделить ожидаемые ошибки, с которыми я хочу работать локально, от непредвиденных ошибок, с которыми нужно обращаться в процедуре обработки ошибок (или в другом месте)?

On Error GoTo ErrorHandler

'Some code...

'Here I want to trap a likely/expected error locally, because the same
'error may occur elsewhere in the procedure but require different handling.
On Error Resume Next
personIndex = FindInArray(personName, personArray)
If Err.Number = ERR__ELEMENT_NOT_FOUND_IN_ARRAY Then
    MsgBox "Name not found in person array. Using default person."
Else
    'What if it a different kind of error?
    ' .e.g. ERR__ARRAY_CONTAINS_TWO_PERSONS_WITH_SAME_NAME
    'I want to rethrow it, but can't because On Error Resume Next swallows it.
End If
On Error GoTo ErrorHandler 'back to normal
'I can't rethrow it here either, because On Error Goto cleared the Err object.

'-----------------------
ErrorHandler:
Select Case Err.Number
Case ERR__ELEMENT_NOT_FOUND_IN_ARRAY
    'The error number doesn't give me enough info 
    'to know what to do with it here!
Case ERR__ARRAY_CONTAINS_TWO_PERSONS_WITH_SAME_NAME
    'Existing code to deal with this error
Case ...

Я думаю, что я мог бы "сохранить" номер ошибки, источник, описание и т.д. в какой-либо другой переменной/объекте и использовать их для повышения ошибки после On Error GoTo ErrorHandler 'back to normal (и на самом деле я реализовал это просто, чтобы увидеть), но это кажется ужасно неудобным и неуклюжим.

Ответ 1

Этот ответ - мое мнение о проблеме, которая, возможно, рассматривается несколько иначе.

При рассмотрении этого блока кода:

On Error Resume Next
personIndex = FindInArray(personName, personArray)
If Err.Number = ERR__ELEMENT_NOT_FOUND_IN_ARRAY Then
    MsgBox "Name not found in person array. Using default person."
Else
End If

Вы упомянули: "ожидаемые ошибки" в названии.
Но дело в том, что никакой ошибки не следует бросать, если вы заранее знаете, что это может произойти.
Они представляют собой форму validation, которая, на мой взгляд, должна быть встроена в функции в виде условных утверждений.

Ранее упомянутый блок кода будет примерно таким же, как на базовом уровне:

    If Not (in_array(vArray, "Jean-Francois")) Then
        MsgBox "Name not found in person array. Using default person."
    End If

Что, на мой взгляд, намного чище и удобочитаемо. С пользовательской функцией, которая не является частью базового кода, но это делает ваш чек за кулисами. Многоразовые функции могут быть обернуты в модуль, который вы используете таким образом, который очень похож на статический класс.

Public Function in_array(vArray As Variant, sItem As String) As Boolean

    Dim lCnt As Long

    in_array = False
    Do Until lCnt = UBound(vArray) + 1
        If StrComp(vArray(lCnt), sItem, CompareMethod.Text) = 0 Then
            in_array = True
            Exit Function
        End If
        lCnt = lCnt + 1
    Loop

End Function

Еще лучше было бы использовать функцию in_array() из функции findInArray() и иметь только 1 строку кода в basubub, которая будет:

personIndex = FindInArray(personName, personArray)

Пусть функции в обратном дескрипторе остальное и перехватывают исключения, которые вы можете предвидеть.
Это всего лишь пример, очевидно, что вы пишете функции и возвращаете значения, которые вам полезны, и вы, вероятно, можете добавить более обширную проверку.

Моя точка зрения заключается в том, что эти возвращаемые значения являются сообщениями возврата, которые являются частью логики приложения/проверки, я не вижу в них технических ошибок - следовательно, я не вижу никакой пользы от использования обработчика ошибок для них поскольку настраиваемая функция точно соответствует вашим потребностям (по моему мнению) гораздо более чистой структурой.

Я считаю это технической ошибкой, когда вы передаете, например, три аргумента в вызов функции, в то время как он принимает только два. Обработчик ошибок уведомляет вас, после чего разработчик может решить сделать текущую функцию более динамичной, допустив, например. дополнительные параметры и исправление ошибки.

Ответ 2

Я создал пользовательский тип, который имеет те же элементы, что и объект Err (число, источник, описание и т.д.). Функция SaveErr будет в основном копировать значения свойств объекта Err в переменную этого типа, а RaiseSavedErr вызовет ошибку, используя эти значения свойств.

Конечно, то же самое можно было бы сделать, используя класс и методы вместо пользовательского типа и функций /subs. Но идея была бы такой же.

Пример:

    On Error Resume Next
    personIndex = FindInArray(personName, personArray)
    savedErr = SaveErr(Err) 'Save values of Number, Source, Description, etc.
    On Error GoTo ErrorHandler
    'Segregate error handling strategies here using savedErr
    If savedErr.Number = ERR__ELEMENT_NOT_FOUND_IN_ARRAY Then
        MsgBox "Name not found in person array. Using default person."
    Else
        RaiseSavedErr savedErr 'rethrows the error
    End If

Я хотел бы знать, есть ли более стандартный или элегантный способ сделать это.

Ответ 3

Вкл. Ошибка Продолжить дальше - корень всего зла в VBA;)

Я не видел весь ваш код, но то, что вы задали в вопросе, может быть легко удовлетворено с помощью MULTIPLE ERROR HANDLERS и RESUME. Это намного проще, чем создание настраиваемого объекта Err и повышение ошибок...

Public Sub sixsixsixBytes()
    On Error GoTo COMMON_ERROR_HANDLER
   'Some code...

    On Error GoTo ARRAY_ERROR_HANDLER
    Call Err.Raise(123)  'lets say error occured in personIndex = ....
    'it will jump to 2nd error handler and come back
    'some code again... 
    'If statement is not required at all
    Call Err.Raise(666)

    On Error GoTo COMMON_ERROR_HANDLER:
    'some code again...
    Call Err.Raise(234)
    Exit Sub

'# MULTIPLE ERROR HANDLERS
COMMON_ERROR_HANDLER:
    Select Case Err.Number
           Case 234: MsgBox 234
           Case 345: MsgBox 345
    End Select

ARRAY_ERROR_HANDLER:
    Select Case Err.Number
           Case 123:
                MsgBox "Name not found in person array. Using default person."
                Resume Next 'or Resume after changing a value (as per your need)
           Case 666:
                MsgBox "Some other error"
                Resume Next
    End Select
End Sub

Ответ 4

Хотя меня немного смущает заданный вопрос (и я читал его довольно много раз к настоящему времени:-)), у меня очень сильное ощущение, что источник этой дилеммы лежит в пределах области действия.
Если все в порядке, я буду использовать некоторые базовые примеры, которые показывают шаблон, но не 1-1 наравне с вашим кодом.

Как отделить ожидаемые ошибки, с которыми я хочу работать локально, от непредвиденных ошибок, с которыми приходится обращаться в процедуре обработки ошибок (или в другом месте)?

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

Я нахожу, что если я столкнулся с некоторыми ожидаемыми номерами ошибок локально, я не могут легко "свергнуть" неожиданные номера ошибок, с которыми нужно обращаться в другом месте.

Вы можете, если вы делегируете код, который вы хотите проверить на локальные ошибки, на внешние функции/подпрограммы, которые вы размещаете поверх определенного уровня в call stack. Поскольку они обрабатывают ошибки в пределах их собственного объема, они не будут смешиваться друг с другом.

Рассмотрим этот код:

Sub baseSub()

    Dim n As Integer

    n = checkDivision(1, 0)      
    n = 1 / 0  ' cause an error

End Sub

Public Function checkDivision(iNumerator As Integer, iDenominator As Integer)

    On Error Resume Next
    checkDivision = iNumerator / iDenominator

    If Err.Number <> 0 Then
        checkDivision = Err.Number
        Exit Function
    End If

End Function

Напротив: при применении On Error Resume Next от baseSub все функции, которые помещаются поверх стека вызовов, также игнорируют ошибки. Но это не работает наоборот.

Я думаю, вы можете использовать это в своих интересах.

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

Если это не сработает, тогда у меня нет идей.