显然,错误处理通常是一个大主题,最佳实践在很大程度上取决于您正在使用的语言的功能以及您正在编码的例程如何与其他例程相适应。因此,我将把我的答案限制为 VBA(在 Excel 中使用)和您所描述的那种库类型例程。
库例程中的异常与错误代码
在这种情况下,我会not使用返回码。 VBA 支持一种异常处理形式,虽然不如 C++/Java/??.NET 中更标准的形式强大,但非常相似。因此,这些语言的建议通常适用。您可以使用异常来告诉调用例程,被调用例程由于某种原因无法完成其工作。您可以在最低级别处理异常,在这里您可以对失败做一些有意义的事情。
Bjarne Stroustrup 在本书中很好地解释了为什么在这种情况下异常比错误代码更好。 (这本书是关于 C++ 的,但是 C++ 异常处理和 VBA 错误处理背后的原理是相同的。)
http://www2.research.att.com/~bs/3rd.html http://www2.research.att.com/~bs/3rd.html
以下是第 8.3 节的精彩摘录:
当一个程序由单独的
模块,尤其是当那些
模块来自单独开发
库,错误处理需要
分为两个不同的部分:[1]
错误情况的报告
无法在本地解决 [2]
处理其他地方检测到的错误
库的作者可以检测到
运行时错误,但通常不会
知道该怎么办。
图书馆的用户可能知道如何
处理此类错误但不能
检测到它们——否则它们就会
在用户代码中处理,而不是
留给图书馆去寻找。
第 14.1 节和第 14.9 节还讨论了库上下文中的异常与错误代码。 (archive.org 上有这本书的在线副本。)
stackoverflow 上可能有更多关于此的信息。我刚刚发现这个,例如:
异常、错误代码、断言 https://stackoverflow.com/questions/1388335/exception-vs-error-code-vs-assert/1388482#1388482
(可能存在涉及正确管理资源的陷阱,在使用异常时必须清除这些陷阱,但它们并不真正适用于此。)
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 错误“冒泡” https://stackoverflow.com/questions/1418777/vba-error-bubble-up/1419147#1419147)。调用者可能会这样做:
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 与异常抛出 https://stackoverflow.com/questions/1467568/debug-assert-vs-exception-throwing/1468385#1468385
我在这里举了一个例子:
断言是邪恶的吗? https://stackoverflow.com/questions/1854302/is-assert-evil/1894867#1894867
关于通用数组处理例程的一些 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