处理数学函数中的错误

发布于 2024-10-07 20:14:13 字数 962 浏览 0 评论 0原文

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

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

我愿意接受建议!

What is good practice for error handling in math-related functions? I'm building up a library (module) of specialized functions and my main purpose is to make debugging easier for the code calling these functions -- not to make a shiny user-friendly error handling facility.

Below is a simple example in VBA, but I'm interested in hearing from other languages as well. I'm not quite sure where I should be returning an error message/status/flag. As an extra argument?

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

or something like the following. The function returns a boolean "success" flag (as in the example below, which would return False if the input arrays weren't numeric etc.), or an error number/message of some other type.

Function AddArrays(arr1, arr2, result) As Boolean

    ' same code as above

    AddArrays = booSuccess

End Function

However I'm not too crazy about this, since it ruins the nice and readable calling syntax, i.e. can't say c = AddArrays(a,b) anymore.

I'm open to suggestions!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

鸢与 2024-10-14 20:14:13

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

如果此例程没有捕获错误,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

“做一些有意义的事情”的含义取决于应用程序的上下文。在上面的调用者示例中,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 异常抛出

我在这里给出了一个例子:

断言是邪恶的吗?

关于通用数组处理例程的一些 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

Obviously error handling in general is a big topic, and what the best practice is depends a lot on the capabilities of the language you're working with and how the routine you're coding fits in with other routines. So I'll constrain my answer to VBA (used within Excel) and library-type routines of the sort you're describing.

Exceptions vs. Error Codes in Library Routines

In this case, I would not use a return code. VBA supports a form of exception handling that, while not as powerful as the more standard form found in C++/Java/??.NET, is pretty similar. So the advice from those languages generally applies. You use exceptions to tell calling routines that the called routine can't do it's job for whatever reason. You handle exceptions at the lowest level where you can do something meaningful about that failue.

Bjarne Stroustrup gives a very good explanation of why exceptions are better than error codes for this kind of situation in this book. (The book is about C++, but the principles behind C++ exception handling and VBA error handling are the same.)

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

Here is a nice excerpt from Section 8.3:

When a program is composed of separate
modules, and especially when those
modules come from separately developed
libraries, error handling needs to be
separated into two distinct parts: [1]
The reporting of error conditions that
cannot be resolved locally [2] The
handling of errors detected elsewhere
The author of a library can detect
runtime errors but does not in general
have any idea what to do about them.
The user of a library may know how to
cope with such errors but cannot
detect them – or else they would be
handled in the user’s code and not
left for the library to find.

Sections 14.1 and 14.9 also address exceptions vs. error codes in a library context. (There is a copy of the book online at archive.org.)

There is probably lots more about this on stackoverflow. I just found this, for example:

Exception vs. error-code vs. assert

(There can be pitfalls involving proper management of resources that must be cleaned up when using exceptions, but they don't really apply here.)

Exceptions in VBA

Here is how raising an exception looks in VBA (although the VBA terminology is "raising an error"):

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

If this routine doesn't catch the error, VBA will give other routines above it in the call stack a chance to (See this: VBA Error "Bubble Up"). Here is how a caller might do so:

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

What "do something meaningful" means depends on the context of your application. In the case of my caller example above, it decides that some errors should be handled by returning an error value that Excel can put in a worksheet cell, while others require a nasty alert. (Here's where the case of VBA within Excel is actually not a bad specific example, because lots of applications make a distinction between internal and external routines, and between exceptions you expect to be able to handle and error conditions that you just want to know about but for which you have no response.)

Don't Forget Assertions

Because you mentioned debugging, it's also worth noting the role of assertions. If you expect AddArrays to only ever be called by routines that have actually created their own arrays or otherwise verified they are using arrays, you might do this:

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

A fantastic discussion of the difference between assertions and exceptions is here:

Debug.Assert vs Exception Throwing

I gave an example here:

Is assert evil?

Some VBA Advice About General Array Handling Routines

Finally, as a VBA-specific note, there are VBA variants and arrays come with a number of pitfalls that must be avoided when you're trying to write general library routines. Arrays might have more than one dimension, their elements might be objects or other arrays, their start and end indices might be anything, etc. Here is an example (untested and not trying to be exhaustive) that accounts for some of that:

'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
淡紫姑娘! 2024-10-14 20:14:13

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

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

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

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

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

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

示例:假设您想要捕获 VBA 通常无法捕获的错误,即整型变量的值应始终大于 2。在代码中的某个位置,输入 If intvar<=2 then goto EH。然后在 EH 步骤中添加 If intvar<=2 then msgbox "Intvar=" &整数。

There's lots of ways to trap errors, some better than others. Alot of it depends on on the nature of the error and how you want to handle it.

1st: In your examples, you aren't handling the basic compiling & runtime errors (see code below).

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

2nd: Using the framework example above, you can add all the if-then logical error trapping statements you want & feed it to the EH step. You can even add multiple EH steps if your function is complex enough. Setting things up this way allows you to find the particular function where your logic error occurred.

3rd: In your last example, ending that function as a boolean is not the best method. If you were able to add the 2 arrays, then that function should return the resultant array. If not, it should throw up a msgbox-style error.

4th: I recently started doing a little trick that can be very helpful in some situations. In your VBA Editor, go to Tools->Options->General->Break on ALL errors. This is very helpful when you already have your error handling code in place, but you want to go the exact line where the error occurred and you don't feel like deleting perfectly good code.

Example: Let's say you want to catch an error that wouldn't be caught normally by VBA, i.e. an integer variable should always have a value >2. Somewhere in your code, say If intvar<=2 then goto EH. Then in your EH step, add If intvar<=2 then msgbox "Intvar=" & Intvar.

故人如初 2024-10-14 20:14:13

首先,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,然后运行 ​​< code>Exit Function 语句 &继续前进(与使用 End Function 删除底部相同)。但是,当您进行调试并获得该 MsgBox 时,您可以手动执行 Ctrl-Break,然后将下一条语句 (Ctrl-F9) 设置为未修饰的 Resume 并按 F8 继续执行 - 它会继续回到引发错误的行。您甚至不必删除额外的 Resume 语句,因为如果没有手动干预,它们将永远执行。

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

First, PowerUser gave you a good answer already--this is an expansion on that one.

A trick that I just learned is the "double resume", thus:

Function Foobar (Arg1, Arg2)
On Error goto EH
   Do stuff

FuncExit:
  Exit Function
EH:
   msgbox "Error" & Err.Description
   Resume FuncExit
   Resume
End Function 

What happens here is that in the execution of finished code, your code throws up a MsgBox when an error is encountered, then runs the Exit Function statement & goes on its way (just the same as dropping out the bottom with End Function). However, when you're debugging and you get that MsgBox you instead do a manual Ctrl-Break, then set next statement (Ctrl-F9) to the unadorned Resume and press F8 to step--it goes right back to the line that threw the error. You don't even have to take the extra Resume statements out, since they will never execute without manual intervention.

The other point on which I want to argue (gently) with PowerUser is in the final example. I think it's best to avoid unneeded GoTo statements. A better approach is If intvar<=2 then err.raise SomeCustomNumber. Make sure you use a number that isn't already in use--search 'VB custom error' for more information.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文