处理数学函数中的错误

Jea*_*ett 3 math error-handling vba

数学相关函数中错误处理的好习惯是什么?我正在建立一个专门的函数库(模块),我的主要目的是让调用这些函数的代码更容易调试 - 而不是制作一个闪亮的用户友好的错误处理工具.

下面是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
Run Code Online (Sandbox Code Playgroud)

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

Function AddArrays(arr1, arr2, result) As Boolean

    ' same code as above

    AddArrays = booSuccess

End Function
Run Code Online (Sandbox Code Playgroud)

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

我愿意接受建议!

jto*_*lle 10

显然,错误处理通常是一个很大的主题,最佳实践取决于您正在使用的语言的功能以及您编写的例程如何适应其他例程.因此,我将限制我对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上可能有更多这方面的内容.我刚发现这个,例如:

异常与错误代码与断言

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

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
Run Code Online (Sandbox Code Playgroud)

如果此例程没有捕获到错误,VBA将在调用堆栈中给出其上方的其他例程(参见:VBA错误"冒泡").以下是调用者可能会这样做的方式:

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
Run Code Online (Sandbox Code Playgroud)

什么"做有意义的事情"意味着取决于您的应用程序的上下文.对于上面的调用者示例,决定应该通过返回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
Run Code Online (Sandbox Code Playgroud)

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

Debug.Assert vs Exception Throwing

我举了一个例子:

是断言邪恶?

关于通用数组处理例程的一些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
Run Code Online (Sandbox Code Playgroud)

  • 嗯,只是退出不会引发错误;该函数只会返回 Empty。你可以像你说的那样返回#N/A,但这两个选项都会让我们回到原来的问题。您有两种“返回值”,一种是添加数组的结果,另一种是“错误”结果(无论是 False、Empty 还是 #N/A)。每次调用 AddArrays 都需要显式检查错误结果。异常系统的要点是能够将“一切正常”代码的主线与“出了问题”代码分开。 (2认同)