有没有任何理由抛出DivideByZeroException?

21

有没有一些情况下,抛出可以避免的错误是一个好主意呢?

我特别考虑了 DivideByZeroExceptionArgumentNullException

例如:

double numerator = 10;
double denominator = getDenominator();

if( denominator == 0 ){
   throw new DivideByZeroException("You can't divide by Zero!");
}

有什么原因会导致出现这样的错误吗?

注意:我不是在谈论捕获这些错误,而是特别想知道是否有正当理由抛出它们。

再次强调:

我知道在我给你的例子中,最好处理这个错误。也许问题应该重新表述。是否有任何理由在此处抛出这些错误,而不是处理它们。


代码合约可能会缓解这个问题。 - Hamish Grubijan
注意:Juhar和nobugz的答案都非常好。 - Armstrongest
16个回答

20

假设你编写了一个用于处理不能适应Int64的非常大整数的库,那么你可能想要在你编写的除法算法中抛出DivideByZeroException异常。


你能详细说明一下吗?听起来像是一个有趣的边缘情况。 - Armstrongest
6
@Atomiton - 我以前确实做过这件事。如果您编写了一个数学库,而调用代码提供了零作为分母,那么除了抛出异常,您还能做什么呢?数学库并不知道您试图做什么。在那个位置上,没有一种总是正确的处理情况的方法。只有调用代码拥有足够全局视野来知道该怎么做。因此,请抛出该异常。 - Jeffrey L Whitledge
1
@Atomiton基本上,如果您想要超出Int64范围的数字精度,您需要编写自己的库(或查找现有的库)。如果您编写一个库,您将创建一些类,您希望在这些类上执行典型的数学运算,例如除法。您需要提前检查除以零的情况,并抛出DivideByZeroException异常,以让库的用户知道他们正在尝试进行“异常”操作。 - juharr
谢谢juharr。仔细想了想,这现在更有意义了。我回忆起很久以前上计算机课时在Java中编写BigInt类的经历。 - Armstrongest
@Malfist:是的,我认为我们必须像建立自己的一样来处理它...以及我们如何处理它。那是很久以前的事了,我的记忆有点模糊。 - Armstrongest
显示剩余2条评论

14

.NET运行时已经非常擅长抛出这些异常了。它们也非常描述出问题所在,您无法为异常消息添加任何有价值的注释。 "您不能将零作为除数" 毫无价值。

但是,通过筛查客户端代码传递的值,您可以避免许多此类异常。如果客户端代码传递了 null,则应使用带有参数名称的ArgumentNullException;如果传递了可能会导致 DivideByZero 问题的内容,则应使用ArgumentException。


我删掉了我的回答并投了你一票...你说得比我清楚。赞。 - Beska
绝对正确。即使你正在编写一个数学库,也不应该抛出DivideByZeroExceptions异常。出于同样的原因,你也不应该抛出NullReferenceException异常,基本上:这些类型的异常表明“库有缺陷”。你想要表达的是“你在错误地使用库”。ArgumentExceptions异常是完美的选择。 - Joren
1
如果您想将异常冒泡到用户,并使用异常消息向用户显示他犯了什么错误,那么提供自定义消息可能是一个好主意。例如 LiteralErrorMsg1.Text = ex.Message。另一种情况是本地化。您可能还希望以用户的语言显示错误消息。 - Armstrongest
糟糕!我纠正了问题。我的意思是 ArgumentNullException,而不是 NullReferenceException - Armstrongest
1
@Atomiton:现在你的问题不再有意义了。 ArgumentNullException总是由throw语句引发。DivideByZeroException则是由CPU引发的。这是两回事。 - Hans Passant

5

这样做绝对有原因,就像大多数异常抛出问题一样,你必须考虑作为一个团队中的开发者。你所编写的代码可能是另一个系统的一部分,也可能是一个库或组件。

如果你编写了一个数学库,并且有一个名为Divide(int a, int b)的函数用于除法运算,如果有人想使用你的方法并传入0,那么你会希望抛出一个异常,以便他们作为开发者知道他们犯了错误。

如果你不抛出异常,就会在你的代码中引入漏洞。如果我使用了你的除法方法,并将值10和0放入其中,这将产生10/0,从而引发异常,唯一正确的答案是错误。如果你决定更改值,使其不会出现错误,或者返回0,那不是我期望的结果——我认为我的除法运算完美无缺,从未注意到问题。我可能会拿着这个0去进行其他计算,毫不知情地使用了错误的答案,因为10/0不是0。


10D/0D = double.Infinity,不是错误。根据应用程序的性质,这可以呈现为无限符号(∞)或呈现为错误。 - Paul Ruane
啊!很棒的答案!所以……在你想要将异常向上抛出而不是处理它的情况下,你会抛出错误! - Armstrongest
但是,如果你可以直接运行除法并让它为你抛出异常,那么检查和手动抛出异常的意义何在呢? - Paul Creasey

4

您只应使用异常处理异常情况。

在这种情况下,看起来零将是无效的用户输入。您应该检查无效的用户输入并相应地处理到达适当的异常点之前。

只要正确处理输入,就没有理由发生DivideByZeroException。如果您在验证输入后仍收到DivideByZeroException,则说明您有一个真正的问题(在这种情况下,异常是适当的)。


2
虽然我赞同您的一般观点,但我认为“例外只适用于‘特殊情况’”这句老话被过分强调了。克日什托夫·索瓦里纳(Krzysztof Cwalina)肯定也是这样认为的- http://blogs.msdn.com/kcwalina/archive/2008/07/17/ExceptionalError.aspx - Dan Diplo
在我看来,Krzysztof 只是重新阐述了我认为的“异常情况”的定义(即发生真正错误的情况,而不是可以通过输入验证等轻松捕获的情况) @Dan。 - Justin Niessner
但是如果你正在编写一个被其他人使用的库呢?我认为这就是关键。当涉及到某人如何使用他们的API时,库的编写者无法事先确定什么是异常情况,什么是常见情况。 - Dan Diplo
是的,这就是我所想的。有几个人说在编写库时会用到它。显然,在正常情况下,要么处理零,要么让 .Net 抛出错误。;-) - Armstrongest
@Atomiton - 编写库是正常情况。典型的应用程序至少有三个层次结构,只有用户界面层可以拥有所有私有方法。每个层次结构可能包含许多模块。因此,在正常应用程序中,只有很小一部分对象可以避免在公共方法中验证输入并抛出异常。 - Jeffrey L Whitledge
显示剩余2条评论

3
如果你正在讨论直接与UI互动的代码,那么不需要。我想不出任何理由为什么你会这样做。
然而,如果你正在构建一个被其他开发人员使用的类/对象,并且他们传入了明显愚蠢的数据,则在验证过程中抛出适当的错误。

2
如果您正在编写一个BigInteger库,那么绝对应该抛出适当的DivideByZero异常。如果您正在编写一个购物车库,那么...嗯,可能不需要。
我目前想不到抛出NullReferenceException的好理由。如果您创建了一个API,并在文档中说明“HummingBirdFeeder.OpenSugarWaterDispenser(Dispenser dispenser,int flowRate)的第一个参数是您希望打开的糖水分配器。”如果有人随后将null值传递给此方法,则您绝对应该抛出ArgumentNullException。
让NullReferenceException从您的API中退出只会是懒惰,因为那是错误的,会泄漏一些内部信息。
编辑后添加:
现在您将问题更改为引用ArgumentNullException,那么答案很简单:是的,在这些情况下,您绝对应该抛出这种类型的异常:
- 您正在编写一个公共方法。 - 获取参数的null值在某种程度上是不合适的(即,没有特定的参数,该方法就没有意义)。 - 该方法的文档指示不允许使用null值。
在这种情况下,您的方法应该做的第一件事就是检查null值并在违反条件时抛出异常。
如果您正在编写私有或内部方法,则在大多数情况下,您不需要在发布版本中在运行时验证参数。您可以确保自己的代码正确调用自己的代码。有助于创建这种保证的一件事是在调试版本中通过添加断言来验证参数。
Debug.Assert(dispenser != null);

这样,您就可以验证代码是否正确运行,并在不用大量无用、冗余检查的情况下及早捕捉错误,而不会减缓发布代码的速度。


谢谢!实际上,我打的是NullReferenceException...但是我想的是ArgumentNullException。谢谢!我会更正问题的! - Armstrongest

1
在你自己的代码中,我怀疑异常并没有太多用处。然而,如果你正在编写一个将被他人使用的库,那么你应该使用异常来将错误信息传递回消费你库的代码。

"在框架中,异常被用于处理硬错误和逻辑错误。起初,采用异常处理作为报告所有功能性失败的手段可能会有些困难。然而,设计框架的所有公共方法以通过抛出异常来报告方法失败是非常重要的。"

Krzysztof Cwalina, Designing Reusable Frameworks


1

标题有点误导,它不是关于抛出(特殊的)DivideByZeroException,而是关于这个问题:

在验证用户输入时,我应该使用异常吗?

这可能已经被问过了。我的看法是:不要,只需使用让您可以使用错误代码或布尔值处理此问题的流程。

但是,在应用程序的更深层中验证“数据”时,抛出异常通常是正确的做法。


谢谢您的评论。但是,我想知道是否有抛出明显易于检查的错误的情况,因此我特别提到了这个问题。深层次概念(已经由一些人回答)是我正在寻找的东西。 - Armstrongest

1
如果您正在编写某种数值类,并且可以预期调用您的类的代码已执行验证,那么我可以看到抛出DivisionByZeroException可能是有用的,但在几乎任何其他情况下,最好在操作之前进行检查并根据需要抛出ArgumentException或ArgumentOutOfRange异常。
NullReferenceException是框架使用的保留异常之一,您不应该通过公共API抛出它,还有IndexOutOfRange、AccessViolationException和OutOfMemoryException。请参阅《框架设计指南》书籍或代码分析警告说明

谢谢。我已经纠正了问题(+1提醒了我)。我的意思是 ArgumentNullException - Armstrongest
如果您正在实现自己的索引器、列表、字典或哈希表,抛出“IndexOutOfRange”会有什么问题? - Matthew Whited
@Matthew如果你查看IndexOutOfRange的MSDN文档,它明确说明了“尝试访问数组元素时引发的异常”(http://msdn.microsoft.com/en-us/library/system.indexoutofrangeexception.aspx)。我们无法实现自己的数组 (无法从System.Array继承),因此使用它将与异常文档和API用户相矛盾。 - Ian G
很高兴看到人们因为MSDN的圣经而害怕使用某些东西。未找到的索引器本质上是超出范围的。因此,抛出IndexOutOfRange异常是完全有道理的。但是请随意抛出ArgumentOutOfRangeException或任何您想要的异常。 - Matthew Whited

0

我想不出有什么例外情况。我会直接编写程序来避免异常情况的发生。异常处理可能会很耗费资源,所以如果你可以通过防御性编程来避免异常情况,那就这样做,而不是抛出异常。

至少这是我的高级开发人员在很久以前当我试图捕获DivideByZeroException时告诉我的。


2
你应该进行防御性编程,但这并不意味着你应该假定它永远不会发生而不去捕获它。你可能非常出色,能够处理每个UI情况...但是,后来,有人向UI添加了一些新功能,它们并不那么智能...崩溃了。如果你能优雅地捕获异常,进行必要的通知和日志记录,然后继续执行,这可能是一个好主意。 - Beska
好的观点。但是如果你总是这样做:if(分母== 0){},那么你就不会有问题了。但我理解你的意思。 - Jack Marchetti
@Beska - 不,你永远不应该在无效或未知状态下继续程序。当发生意外错误时,应发出适当的通知并记录,然后程序应该突然死亡。“优雅”地处理错误是导致错误代码和维护困难的捷径。 - Jeffrey L Whitledge
1
@Jeffrey:当然,你不应该在无效或未知状态下继续。这就是为什么我说“如果你可以优雅地捕获……”显然,你不能像什么都没发生一样捕获并继续,但在某些情况下,有办法继续(查询新的正确输入并重试等)。 - Beska
1
@Jack:是的,在这种情况下有点简化了...就像你说的,检查“if(denominator==0)”应该可以确保参数无效。但我也见过一些情况,人们确信他们已经覆盖了所有基础并且没有错误数据可以通过...最终被奇怪的边缘情况、栅栏条件等证明是错误的。 - Beska
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接