模式在本地处理预期错误,重新抛出意外错误

时间:2013-10-30 11:31:42

标签: vba error-handling

有时某些代码会以预期的方式引发错误,并且最方便的是在本地处理它而不是将其抛给错误处理例程,在那里它会与其他相同类型的错误混淆。但你不希望吞下意外的错误;你希望他们像往常一样被抚养长大。

在下面的(略设的)示例中,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's 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之后使用它们引发错误,(事实上我已经实现了这个看)但这看起来非常不方便和笨拙。

4 个答案:

答案 0 :(得分:4)

这个答案是我对手头问题的看法,也许是从略微不同的角度来看。

在考虑这段代码时:

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()函数,在baseub中只有一行代码,这将是:

personIndex = FindInArray(personName, personArray)

让后面的函数处理其余部分,并拦截您可以预见的异常 这只是一个示例,显然您编写了对您有用的函数和返回值,您可以添加更广泛的验证。

我的观点是这些返回值是返回消息,它们是应用程序/验证逻辑的一部分,我不认为它们是技术错误 - 因此,我认为使用错误处理程序没有任何好处作为一个自定义创建的功能完全符合您的需求(我认为)更清洁的结构。

当你将三个参数传递给函数调用时,我认为这是一个技术错误,而它只接受两个参数。错误处理程序通知您,之后开发人员可以决定通过允许例如使当前函数更具动态性。可选参数并修复bug。

答案 1 :(得分:3)

我创建了一个用户定义的类型,其成员与Err对象(Number,Source,Description等)相同。 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

我想知道是否有更标准或更优雅的方式来做到这一点。

答案 2 :(得分:3)

On Error Resume Next 是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

答案 3 :(得分:3)

虽然我对所提出的问题感到有些困惑(我现在已经阅读了很多次:-)),但我非常强烈地认为这种困境的根源在于功能范围。
如果没关系,我将使用一些显示模式的基本示例,但不会与您的代码相提并论。

  

如何隔离我想在本地处理的预期错误,   从意外错误中处理错误处理例程(或   其他地方)?

我觉得答案就在于问题本身 错误处理程序在您从较低级子例程或函数调用的子例程/函数的本地范围内起作用。

  

我发现如果我在本地处理一些预期的错误号,我   不能轻易“重新抛出”要处理的意外错误号   别处。

如果您要将检查本地错误的代码委托给放在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

相反:当从 a baseSub应用On Error Resume Next时,放置在调用堆栈顶部的所有函数也将忽略错误。但是,它反过来不起作用。

我认为你可以利用这个优势。

  

总而言之,我相信你可以通过诱捕来解决问题   您放置在更高级别的委派函数中的预期错误   调用堆栈。

如果这不起作用,那么我就没有想法了。