我进行了一些谷歌搜索,但没有立即找到答案。是否有任何好的定义、经验法则或指南比意见或偏好更加客观?如果一个人说“这是例外情况”,而另一个人说“不,不是”,如何解决这个争议?
编辑:哇,已经有很多答案了,有人持不同意见,并且它们似乎都是回答者的观点 :P 没有人引用维基百科页面或一位资深程序员的文章。我从中得出的“元答案”是并没有对于何时使用异常情况达成一致的经验法则。因此,我感到更有信心在选择抛出异常时采用自己个人的特殊规则。
我不同意其他答案关于某些情况需要“特殊”才能引起“异常”的说法。我认为,“特殊”是指任何时候你想通过抛出一个向上传播的对象来打破正常的控制流。
异常是一种语言特性,是达到目的的手段。它们并不神奇。像其它特性一样,使用它们应该根据你需要它们提供的东西的时间来指导,而不是根据它们的名称。
__try
/_catch
是一件好事,但即使缺少这个功能,SEH风格的异常仍然会被抛出。实际上,即使程序是用汇编手工制作的,它们也会被抛出。这就是我所说的操作系统特性;语言依赖于它,但它并不依赖于语言。 - Steven Sudit如果满足以下条件,那么这是一个例外情况:
它是一个失败条件。 并且
它偶尔而又出乎意料地发生。 并且
没有更好的机制来报告它。
编辑
从Dan Weinreb的博客文章中抄袭,Ken在这里发布了相关内容,我想提供以下关于异常的总结。
方法的契约定义了如何(以及是否)信号化异常条件(即故障)。如果该方法表示某个东西是异常情况,那么它就是异常情况。当然,这还留下了如何设计契约的问题。
异常的好处在于不需要调用者进行任何检查,并自然地上升,直到被能够处理它们的东西捕获。它们还可以包含重要的详细信息、用户可读的文本和堆栈跟踪。这些特性使它们非常适合防止进一步处理但不可预测或常见的故障情况,或者在明确的错误处理会对代码流产生干扰的情况下。它们特别适用于“永远不应该发生”的错误,但其影响是灾难性的(例如堆栈溢出)。
标志、错误代码、魔术值(NULL、nil、INVALID_HANDLE等)和其他基于返回的机制不会劫持流程,因此更适合于常见且最好在本地处理的情况,特别是那些可以通过工作来解决失败的情况。由于它们是按照惯例而不是法令操作的,所以您不能指望它们被检测和处理,除非实际使用时无效值可能被设计为引发异常(例如使用INVALID_HANDLE进行读取)。
在健壮的代码中使用异常时,每个方法都应捕获意外异常并将它们包装在契约异常中。换句话说,如果您的方法不承诺抛出NullReferenceException,您需要捕获它并在更通用或更具体的异常中重新抛出它。它们被称为异常,而不是惊喜!
一个通用的经验法则是:
在我看来,如果程序的进一步执行会导致致命错误或不可预测的行为,应该抛出异常。
File.Exists()
会抛出异常,还是你会根据File.Exists()
的返回值来手动抛出异常...无论哪种情况,都要符合特定情境的预期。 - Matthew Whited就我个人而言,我认为这种讨论纯粹浪费时间,“什么是异常”是错误的问题。
异常只是另一种流程控制机制,具有某些优点(控制流可以通过调用堆栈向上多级传递)和缺点(有点冗长,行为不太局部化)。
它是否是正确的选择不应该由其名称来确定。如果异常被简单地称为“bubbleups”,我们会进行这些讨论吗?
object
强制转换为 int
时,如果它实际上是一个 string
,那么我应该怎么做,而不是抛出异常? - Steven Sudit嗯,异常编程案例是指偏离正常流程的程序情况,可能是由于以下原因:
::硬件或软件故障,程序员在给定情况下无法处理。程序员可能不确定在这些情况下该怎么做,因此将其留给用户或调用此代码的工具/库。 ::即使程序员可能不确定他的代码将在哪种环境中使用,因此最好将错误处理留给使用代码的人。
因此,程序的异常情况可能是在不常见的环境或与不常见的交互中使用它。 再次,不常见的交互和未知的环境是从设计师的角度来看的。 因此,从正常情况偏离是异常的,而且它基于程序员的观点和上下文。
这样说是否有点绕?:D
异常在问题的直接范围之外出现时非常有用。它们几乎永远不应该在抛出异常的地方被捕获,因为如果可以在那里满意地处理它们,就可以在不抛出任何东西的情况下满意地处理它们。
一个例子是C++的容器,如果它们无法获得所需的内存,就会抛出bad_alloc
。编写容器的人不知道如果容器无法获得内存应该发生什么。也许这是一件预期的事情,调用代码有替代方案。也许这是可恢复的。也许这是致命的,但应该如何记录?
是的,可以返回错误代码,但它们会被使用吗?我看到很多C内存分配没有测试NULL,并且printf只是丢弃返回值。此外,许多函数没有可区分的错误代码,例如printf为负数和内存分配为NULL。在任何返回值都可能有效的情况下,需要找到一种返回错误指示的方法,这比大多数程序员愿意处理的更加复杂。异常不能被忽略,并且不需要大量的防御性代码。
我看过的最好的讨论是Dan Weinreb的博客:什么是真正的条件(异常)。
这个话题表面上是关于Common Lisp的,它的条件系统类似于更灵活的异常形式,但几乎没有Lisp代码,而且你不需要成为一个Common Lisp程序员就能理解这些概念。
float x;
try {
x = a / b;
{
catch (System.DivideByZeoException) {
x = 0;
}
良好的代码示例:
if (b == 0)
return 0;
异常通常是很昂贵的。
catch
的正确位置应该是在某个地方,你可以半智能地处理这个问题。此外,像你第二个示例中的防御性编码可能会有成本,如果几乎不会抛出异常,那么依赖它可能是划算的。 - David Thornleyb
是否为零是不够的。当a
很大而b
很小时,我们仍可能溢出。 - Steven Sudit