【问题标题】:Handling errors in math functions处理数学函数中的错误
【发布时间】:2011-05-24 17:13:49
【问题描述】:

在数学相关函数中处理错误的最佳做法是什么?我正在构建一个专门的函数库(模块),我的主要目的是使调用这些函数的代码更容易调试——而不是制作一个闪亮的用户友好的错误处理工具。

以下是 VBA 中的一个简单示例,但我也有兴趣了解其他语言。我不太确定应该在哪里返回错误消息/状态/标志。作为一个额外的论点?

Function AddArrays(arr1, arr2)
    Dim i As Long
    Dim result As Variant

    ' Some error trapping code here, e.g.
    ' - Are input arrays of same size?
    ' - Are input arrays numeric? (can't add strings, objects...)
    ' - Etc.

    ' If no errors found, do the actual work...
    ReDim result(LBound(arr1) To UBound(arr1))
    For i = LBound(arr1) To UBound(arr1)
        result(i) = arr1(i) + arr2(i)
    Next i

    AddArrays = result
End Function

或类似以下内容。该函数返回一个布尔“成功”标志(如下例所示,如果输入数组不是数字等,它将返回 False),或其他类型的错误号/消息。

Function AddArrays(arr1, arr2, result) As Boolean

    ' same code as above

    AddArrays = booSuccess

End Function

但是我对此并不太感兴趣,因为它破坏了漂亮且可读的调用语法,即不能再说c = AddArrays(a,b)

我愿意接受建议!

【问题讨论】:

    标签: math vba error-handling


    【解决方案1】:

    有很多方法可以捕获错误,有些方法比其他方法更好。很大程度上取决于错误的性质以及您希望如何处理它。

    1st:在您的示例中,您没有处理基本的编译和运行时错误(请参见下面的代码)。

    Function Foobar (Arg1, Arg2)
         On Error goto EH
         Do stuff
         Exit Function
    EH:
         msgbox "Error" & Err.Description
    End Function
    

    第二个:使用上面的框架示例,您可以添加您想要的所有 if-then 逻辑错误捕获语句并将其提供给 EH 步骤。如果您的功能足够复杂,您甚至可以添加多个 EH 步骤。以这种方式进行设置可以让您找到发生逻辑错误的特定功能。

    3rd:在您的最后一个示例中,将该函数作为布尔值结束并不是最好的方法。如果您能够添加 2 个数组,则该函数应返回结果数组。如果没有,它应该会抛出一个 msgbox 样式的错误。

    4th:我最近开始做一个在某些情况下非常有用的小技巧。在您的 VBA 编辑器中,转到工具->选项->常规->所有错误中断。当您已经准备好错误处理代码,但您想找到发生错误的确切行并且不想删除完美的代码时,这非常有用。

    示例:假设您要捕获 VBA 无法正常捕获的错误,即整数变量的值应始终 >2。在您的代码中的某处,例如If intvar<=2 then goto EH。然后在您的 EH 步骤中,添加 If intvar<=2 then msgbox "Intvar=" & Intvar

    【讨论】:

    • 我和你在一起最多 4 点。看我的回答。
    • 还有#3。我认为你应该让调用者决定如何处理错误。也许它是一个消息框,也许它正在返回一个错误代码,也许它什么都不是,它让错误将堆栈展开到一些有意见的调用者......
    • 啊哈...不知道“打破所有错误”的技巧。我一直省略 EH,因为它使调试变得更加复杂,但这可能会有所帮助。
    【解决方案2】:

    首先,PowerUser 已经给了你一个很好的答案——这是对那个答案的扩展。

    刚刚学到的一个技巧是“双重简历”,因此:

    Function Foobar (Arg1, Arg2)
    On Error goto EH
       Do stuff
    
    FuncExit:
      Exit Function
    EH:
       msgbox "Error" & Err.Description
       Resume FuncExit
       Resume
    End Function 
    

    这里发生的是,在执行完成的代码时,当遇到错误时,您的代码会抛出一个 MsgBox,然后运行 ​​Exit Function 语句并继续前进(就像用 @ 退出底部一样987654323@)。但是,当您在调试并获得 MsgBox 时,您改为执行手动 Ctrl-Break,然后将下一条语句 (Ctrl-F9) 设置为未修饰的 Resume 并按 F8 执行步骤——它会直接返回该行这引发了错误。您甚至不必删除额外的 Resume 语句,因为它们永远不会在没有人工干预的情况下执行。

    我想(温和地)与 PowerUser 争论的另一点是在最后一个示例中。我认为最好避免不必要的GoTo 语句。更好的方法是If intvar<=2 then err.raise SomeCustomNumber。确保使用尚未使用的数字——搜索“VB 自定义错误”以获取更多信息。

    【讨论】:

    • 好点。我几乎不必抛出我自己的错误,但是当我这样做时,我会像你建议的那样提出一个自定义错误。
    【解决方案3】:

    显然,错误处理通常是一个很大的话题,最佳实践在很大程度上取决于您使用的语言的功能以及您编写的例程如何与其他例程相适应。所以我将把我的答案限制在 VBA(在 Excel 中使用)和您所描述的那种库类型例程。

    库例程中的异常与错误代码

    在这种情况下,我会使用返回码。 VBA 支持一种异常处理形式,虽然不如 C++/Java/??.NET 中更标准的形式强大,但非常相似。因此,这些语言的建议通常适用。您使用异常来告诉调用例程被调用的例程无论出于何种原因都无法完成它的工作。您可以在最低级别处理异常,以便对失败做一些有意义的事情。

    Bjarne Stroustrup 在本书中很好地解释了为什么在这种情况下异常比错误代码更好。 (这本书是关于C++的,但是C++异常处理和VBA错误处理的原理是一样的。)

    http://www2.research.att.com/~bs/3rd.html

    这是第 8.3 节的精彩摘录:

    当一个程序由单独的 模块,尤其是当那些 模块来自单独开发 库,错误处理需要 分为两个不同的部分:[1] 报告错误情况 无法在本地解决 [2] 处理在别处检测到的错误 库的作者可以检测 运行时错误,但一般不会 知道该怎么做。 图书馆的用户可能知道如何 应对此类错误,但不能 检测到它们——否则它们会 在用户的代码中处理,而不是 留给图书馆找。

    第 14.1 节和第 14.9 节还讨论了库上下文中的异常与错误代码。 (archive.org 上有这本书的在线副本。)

    在 stackoverflow 上可能有更多关于此的内容。我刚找到这个,例如:

    Exception vs. error-code vs. assert

    (可能存在涉及正确管理资源的陷阱,在使用异常时必须清理这些陷阱,但它们并不真正适用于此。)

    VBA 中的异常

    这是在 VBA 中引发异常的样子(尽管 VBA 术语是“引发错误”):

    Function AddArrays(arr1, arr2) 
        Dim i As Long 
        Dim result As Variant 
    
        ' Some error finding code here, e.g. 
        ' - Are input arrays of same size? 
        ' - Are input arrays numeric? (can't add strings, objects...) 
        ' - Etc. 
    
        'Assume errorsFound is a variable you populated above...
        If errorsFound Then
            Call Err.Raise(SOME_BAD_INPUT_CONSTANT)    'See help about the VBA Err object. (SOME_BAD_INPUT_CONSTANT is something you would have defined.)
        End If
    
        ' If no errors found, do the actual work... 
        ReDim result(LBound(arr1) To UBound(arr1)) 
        For i = LBound(arr1) To UBound(arr1) 
            result(i) = arr1(i) + arr2(i) 
        Next i 
    
        AddArrays = result 
    End Function
    

    如果这个例程没有捕捉到错误,VBA 将给调用堆栈中它上面的其他例程一个机会(参见:VBA Error "Bubble Up")。以下是调用者可能会这样做的方式:

    Public Function addExcelArrays(a1, a2)
        On Error Goto EH
    
        addExcelArrays = AddArrays(a1, a2)
    
        Exit Function
    
    EH:
    
        'ERR_VBA_TYPE_MISMATCH isn't defined by VBA, but it's value is 13...
        If Err.Number = SOME_BAD_INPUT_CONSTANT Or Err.Number = ERR_VBA_TYPE_MISMATCH Then
    
            'We expected this might happen every so often...
            addExcelArrays = CVErr(xlErrValue)
        Else
    
            'We don't know what happened...
            Call debugAlertUnexpectedError()    'This is something you would have defined
        End If
    End Function
    

    “做一些有意义的事情”的含义取决于您的应用程序的上下文。在上面的调用者示例中,it 决定应该通过返回 Excel 可以放入工作表单元格的错误值来处理某些错误,而其他错误则需要一个讨厌的警报。 (这里是 Excel 中的 VBA 案例实际上不是一个糟糕的具体示例,因为许多应用程序区分了内部和外部例程,以及您希望能够处理的异常和您只想知道的错误情况但你没有回应。)

    不要忘记断言

    因为您提到了调试,所以还需要注意断言的作用。如果您希望 AddArrays 只被实际创建了自己的数组或以其他方式验证他们正在使用数组的例程调用,您可以这样做:

    Function AddArrays(arr1, arr2) 
        Dim i As Long 
        Dim result As Variant 
    
        Debug.Assert IsArray(arr1)
        Debug.Assert IsArray(arr2)
    
        'rest of code...
    End Function
    

    关于断言和异常之间区别的精彩讨论在这里:

    Debug.Assert vs Exception Throwing

    我在这里举了一个例子:

    Is assert evil?

    关于通用数组处理例程的一些 VBA 建议

    最后,作为特定于 VBA 的说明,当您尝试编写通用库例程时,VBA 变体和数组带有许多必须避免的陷阱。数组可能有多个维度,它们的元素可能是对象或其他数组,它们的开始和结束索引可能是任何东西,等等。下面是一个示例(未经测试,并非试图详尽),它解释了其中的一些:

    'NOTE: This has not been tested and isn't necessarily exhaustive! It's just
    'an example!
    Function addArrays(arr1, arr2)
    
        'Note use of some other library functions you might have...
        '* isVect(v) returns True only if v is an array of one and only one
        '  dimension
        '* lengthOfArr(v) returns the size of an array in the first dimension
        '* check(condition, errNum) raises an error with Err.Number = errNum if
        '  condition is False
    
        'Assert stuff that you assume your caller (which is part of your
        'application) has already done - i.e. you assume the caller created
        'the inputs, or has already dealt with grossly-malformed inputs
        Debug.Assert isVect(arr1)
        Debug.Assert isVect(arr2)
        Debug.Assert lengthOfArr(arr1) = lengthOfArr(arr2)
        Debug.Assert lengthOfArr(arr1) > 0
    
        'Account for VBA array index flexibility hell...
    
        ReDim result(1 To lengthOfArr(arr1)) As Double
        Dim indResult As Long
    
        Dim ind1 As Long
        ind1 = LBound(arr1)
    
        Dim ind2 As Long
        ind2 = LBound(arr2)
    
        Dim v1
        Dim v2
    
        For indResult = 1 To lengthOfArr(arr1)
    
            'Note implicit coercion of ranges to values. Note that VBA will raise
            'an error if an object with no default property is assigned to a
            'variant.
            v1 = arr1(ind1)
            v2 = arr2(ind2)
    
            'Raise errors if we have any non-numbers. (Don't count a string
            'with numeric text as a number).
            Call check(IsNumeric(v1) And VarType(v1) <> vbString, xlErrValue)
            Call check(IsNumeric(v2) And VarType(v2) <> vbString, xlErrValue)
    
            'Now we don't expect this to raise errors.
            result(indResult) = v1 + v2
    
            ind1 = ind1 + 1
            ind2 = ind2 + 1
        Next indResult
    
        addArrays = result
    End Function
    

    【讨论】:

    • 谢谢。我对 VBA 中的 EH 有一个错误,因为人们总是对它不屑一顾。而且他们几乎总是将其与异常处理形式非常相似(尽管不那么笨重,并且诚然没有 On Error Resume Next 之类的语言)进行比较。
    • 这是一个非常棒的答案!谢谢。
    • 为什么在If errorsFound Then ....你不那么Exit Function好吗?
    • @QHarr,O.K.,思想实验:在我的第一个 AddArrays 示例中,如果函数在发现错误时直接退出,而不是引发错误,会发生什么情况?返回值是多少,你认为它正确吗?调用 AddArrays 的函数将如何使用结果,这是否正确?
    • 我认为它会向用户抛出错误。但是如果你要返回一个变体,你不能在这里定义一个错误,例如CVErr(xlErrNA) 然后退出函数。 err.Raise 是否会停止执行并且是为程序员而不是用户设计的?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-21
    • 1970-01-01
    相关资源
    最近更新 更多