显然,错误处理通常是一个很大的话题,最佳实践在很大程度上取决于您使用的语言的功能以及您编写的例程如何与其他例程相适应。所以我将把我的答案限制在 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