2010-12-13 209 views
3

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

下面是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)了。

我愿意接受建议!

回答

8

很明显,错误处理一般是一个大问题,最佳实践取决于您正在使用的语言的功能以及您编写的例程如何与其他例程匹配。所以我会将我的答案限制在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上有一本在线书的副本。)

关于此问题,可能还有更多关于此问题的信息。我刚刚发现这一点,例如:

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 

什么“做有意义的事情”意味着取决于您的应用程序的上下文。在上面我的调用者示例中,决定应通过返回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 
+0

哇。如果可以的话,我会多次投票。 – RolandTumble 2010-12-13 20:03:40

+0

谢谢。在VBA中我有一个关于EH的屁股,因为人们总是唠叨不休。而且他们几乎总是将它与具有非常相似的语言(虽然不太笨重,并且承认不具有诸如On Error Resume Next之类的)异常处理形式的语言进行比较。 – jtolle 2010-12-14 00:37:33

+0

这是一个屁股答案!谢谢。 – 2010-12-15 09:04:36

2

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

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

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

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

第三:在你的最后一个例子中,将该函数作为布尔值结束并不是最好的方法。如果你能够添加2个数组,那么该函数应该返回结果数组。如果不是,它应该抛出一个msgbox样式的错误。

4th:我最近开始做一些小技巧,可以在某些情况下非常有用。在您的VBA编辑器中,转至工具 - >选项 - >常规 - >解决所有错误。当你已经有了你的错误处理代码时,这是非常有用的,但是你想要发生错误的确切位置,并且你不想删除完美的代码。

例如:假设您想要捕获一个不会被VBA正常捕获的错误,即整数变量的值应该总是> 2。在你的代码的某处,说If intvar<=2 then goto EH。然后在您的EH步骤中,添加If intvar<=2 then msgbox "Intvar=" & Intvar

+0

我和你在一起4.见我的答案。 – RolandTumble 2010-12-13 18:06:59

+0

和#3。我想你应该让调用者决定如何处理这个错误。也许这是一个消息框,也许它是返回一个错误代码,也许它什么都没有,它可以让错误解开堆栈,直到有意见的调用者... – jtolle 2010-12-14 00:26:45

+0

啊哈......不知道“打破所有错误“诡计。我一直在省略EH,因为它使调试更加复杂,但这可能有所帮助。 – 2010-12-15 09:14:40

3

首先,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声明&继续前进(就像剔除底部End Function一样)。但是,当你在调试时,你得到MsgBox而不是手动的Ctrl-Break,然后将下一个语句(Ctrl-F9)设置为未修改的Resume,然后按F8步 - 它会直接返回到抛出的那一行错误。你甚至不需要额外的Resume报表,因为它们将会执行而不是而不需要手动干预。

我想与PowerUser(轻轻地)争论的另一点是最后一个例子。我认为最好避免不必要的GoTo陈述。更好的方法是If intvar<=2 then err.raise SomeCustomNumber。确保你使用了一个尚未使用的号码 - 搜索“VB自定义错误”以获取更多信息。

+0

好点。我几乎不需要抛出自己的错误,但是当我这样做时,我会提出一个像你所建议的自定义错误。 – PowerUser 2010-12-13 18:49:46

+0

Double aha ...我喜欢双重的简历技巧。 – 2010-12-15 09:15:12

+0

好戏!这一直困扰着我... – Gaffi 2012-04-05 11:46:57