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

Posted

技术标签:

【中文标题】模式在本地处理预期错误,重新抛出意外错误【英文标题】:Pattern to handle expected errors locally, rethrow unexpected errors 【发布时间】:2013-11-09 22:54:21 【问题描述】:

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

在下面的(稍微做作的)示例中,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 之后引发错误,(实际上我已经实现了这个只是为了看看) 但这似乎非常不方便和笨拙。

【问题讨论】:

这也可能是您的问题的一部分,但您在寻找哪种 解决方案?你想缩短你的代码吗?还是您想构建一个可重用的库/函数集来处理错误?您会考虑使用外部库将错误传递给并从中获取 something 吗? IE。有一个 COM ErrorHanlder 类并将错误号传递给它,然后发回一些东西。这只会缩短您的代码并为您提供可重用的错误处理程序,但我不确定这是您寻找的解决方案的 type 【参考方案1】:

我创建了一个用户定义类型,它与Err 对象具有相同的成员(编号、来源、描述等)。 SaveErr 函数基本上会将Err 对象属性的值复制到这种类型的变量中,RaiseSavedErr 将使用这些属性值引发错误。

当然,使用类和方法而不是用户定义的类型和函数/子程序可以完成完全相同的事情。但想法是一样的。

例子:

    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】:

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

【讨论】:

“万恶之源”是一个不合理的笼统说法。如果使用得当,On Error Resume Next 很好。事实上,very link you cite 给出了一个合理的例子(也来自同一来源的here)。 好的,所以您已将控制权发送给ARRAY_ERROR_HANDLER,而不是在本地处理错误。一样的区别。如果出现234345 或其他错误等意外错误怎么办?你会将那些错误处理程序从COMMON_ERROR_HANDLER 复制粘贴到ARRAY_ERROR_HANDLER 吗?重复代码是不好的。 (这就是为什么我希望重新抛出错误以让常规错误处理程序像往常一样处理它。) @Jean-FrançoisCorbett:第 1 次:是的,在错误恢复时 Next 绝对可以明智地使用……(世界上所有的枪都只用于自卫,对吧?) 第 2 次:如何可以在 ARRAY_ERR_HANDLER 中引起错误 234 和 345 吗?如果是这样,它不会是一个数组错误。但是让我们说如果它们可以引起那么它将由同一个处理程序处理,如果不是我们总是可以将它重定向到不同的处理程序。 我不确定您是否考虑过这一点。假设错误234 发生在On Error GoTo ARRAY_ERROR_HANDLER 之后。控制权传递给ARRAY_ERROR_HANDLER,它不知道如何处理它。处理234 的代码在COMMON_ERROR_HANDLER 中。这是我的难题。枪的比喻,我不明白。【参考方案3】:

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

考虑这段代码时:

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

更好的是在findInArray() 函数中使用in_array() 函数,并且在basesub 中只有一行代码,即:

personIndex = FindInArray(personName, personArray)

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

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

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

【讨论】:

我很欣赏您的意见,但有时捕获错误是可行的方法 (example)。这就是我的问题所在。 当然没问题。问题中的示例代码并没有真正暗示您共享的异常示例 - 它们似乎建议使用错误处理程序作为解决常见问题的反模式(在数组中查找 - 两个同名的人) - 因此我的困惑。如果我对问题的理解正确,虽然我不确定,但我会尝试制定一个更好的答案,直接解决较低级别的错误处理问题。 转念一想,也许我的东西是一种反模式。我不知道。好吧,赏金必须给某人,而你的回答让我思考最多,所以你去吧! 谢谢@JF。我希望它会有所帮助。【参考方案4】:

虽然我对所提出的问题有点困惑(而且我现在已经阅读了很多次 :-)),但我有一种非常强烈的感觉,即这种困境的根源在于函数范围。 如果没问题,我将使用一些显示模式但与您的代码不一一对应的基本示例。

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

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

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

如果您将要检查本地错误的代码委托给您放置在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 时,所有位于调用堆栈顶部的函数也将忽略错误。但是,反过来也行不通。

我认为您可以利用它来发挥自己的优势。

所以总结一下,我相信你可以通过陷害来解决问题 您放置在更高级别的委托函数中的预期错误 调用堆栈。

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

【讨论】:

以上是关于模式在本地处理预期错误,重新抛出意外错误的主要内容,如果未能解决你的问题,请参考以下文章

Kurento媒体服务器抛出“处理方法时出现意外错误:未找到工厂'PlayerEndPoint''”

使用条件异常处理调试回原始错误,即重新抛出

活动作业抛出意外错误

VBScript 上的错误处理:不抛出错误

Jaxb UnMarshal 错误:意外元素(uri:“”,本地:“processedSalesOrderTypeList”)。预期的元素是

猫鼬模式预保存中的 ESLint 意外“this”错误