特殊错误是否真的例外?

时间:2008-10-08 00:22:11

标签: language-agnostic exception frameworks

我的理解是,普遍的智慧只说在真正特殊条件下使用例外(实际上,我已经多次在SO上看到过这种说法)。

然而,Krzysztof Cwalina说:

  
    

关于例外的最大误解之一是它们是出于“特殊条件”。现实情况是它们用于沟通错误条件。从框架设计的角度来看,没有“特殊条件”这样的东西。条件是否异常取决于使用的上下文,但可重用的库很少知道如何使用它们。例如,对于简单的数据输入应用程序,OutOfMemoryException可能是例外;对于进行自己的内存管理的应用程序(例如SQL服务器)来说,这并不是那么特别。换句话说,一个人的特殊情况是另一个男人的慢性病。

  
然后,他接着说,例外情况应该用于:

  • 使用错误
  • 程序错误
  • 系统故障

考虑到Krzysztof Cwalina是MS CLR团队的PM,我问:你怎么看待他的陈述?

15 个答案:

答案 0 :(得分:23)

这听起来过于简单,但我认为在合适的地方使用异常是有意义的。在Java和Python等语言中,异常非常常见,特别是在某些情况下。例外适用于您希望通过代码路径冒出的错误类型,并强制开发人员明确捕获。在我自己的编码中,我认为正确的时候在错误被忽略时添加异常,或者抛出异常而不是将错误值返回给函数调用等更优雅。

我可以随意考虑的一些最合适的例外地点:

  • NotImplementedException - 非常恰当的指定特定方式的方式 方法或功能不可用,而不是简单地返回而不做 任何东西。
  • OutOfMemory 例外 - 很难想象有更好的处理方法 错误类型,因为它表示进程范围或OS范围的内存分配 失败。当然,这对于处理至关重要!
  • NullPointerException - 访问null变量是程序员的错误,IMO 这是另一个强制错误冒泡到表面的好地方
  • ArrayIndexException - 在像C这样的无法容忍的语言中,缓冲区溢出 是灾难性的。较好的语言可能返回某种类型的空值,或者在 一些实现,甚至包裹数组。在我看来,抛出一个 异常是一种更优雅的回应。

这绝不是一个全面的清单,但希望它说明了这一点。在优雅和合乎逻辑的情况下使用例外。与编程一样,正确工作的正确工具是很好的建议。毫无意义的例外 - 无所事事,但完全忽略一个强大而优雅的工具同样是不明智的。

答案 1 :(得分:11)

对于编写框架的人来说,也许这很有趣。

对于我们其他人来说,这是令人困惑的(也可能是无用的。)对于普通应用程序,必须将异常作为“特殊”情况搁置。例外会中断程序的普通顺序显示。

你应该谨慎地打破常规的从上到下的顺序处理程序。异常处理是 - 故意 - 难以阅读。因此,为标准方案之外的事物保留例外。

示例:不要使用异常来验证用户输入。人们总是犯输入错误。这不是特例,这就是我们编写软件的原因。这就是if语句的用途。

当您的应用程序获得OutOfMemory异常时,抓住它是没有意义的。这是特殊的。 “顺序执行”假设超出了窗口。您的应用程序注定失败,只是崩溃并希望您的RDBMS事务在崩溃之前完成。

答案 2 :(得分:8)

确实很难确定究竟是什么构成了一个“例外条件”,它保证在程序中使用例外。

使用传达错误原因非常有用的一个实例。正如Krzysztof Cwalina的引述所提到的那样:

  

最大的误解之一   关于例外的是它们的用途   “特殊情况。”现实   是他们的沟通   错误条件。

举一个具体的例子,假设我们有一个getHeader(File f)方法从文件中读取一些标题并返回一个FileHeader对象。

尝试从磁盘读取数据时可能会出现一些问题。也许指定的文件不存在,文件包含无法读取的数据,意外的磁盘访问错误,内存不足等。有多种失败方法意味着应该有多种方法来报告出错的地方。

如果未使用异常,但需要使用当前方法签名来传达发生的错误类型,我们最好的办法是返回null。由于获得null信息量不大,我们从该结果中得到的最佳沟通是“发生了某种错误,所以我们无法继续,抱歉。” - 它不会传达错误原因。

(或者,我们可能有FileHeader对象的类常量,它们指示FileNotFound条件等,模拟错误代码,但实际上有一个带有TRUE, FALSE, FILE_NOT_FOUND的布尔类型。)

如果我们得到FileNotFoundDeviceNotReady例外(假设),至少我们知道错误的来源是什么,如果这是最终用户应用程序,我们可以处理错误以解决问题的方式。

使用异常机制提供了一种通信方式,不需要回退使用错误代码来通知不在正常执行流程内的条件。

但是,这并不意味着一切都应该由例外处理。正如S.Lott所指出的那样:

  

不要使用例外来验证用户   输入,例如。人们做   一直都是错误的。那是什么   if语句适用于。

这是一件无法强调的事情。不知道何时使用例外的危险之一是倾向于异常快乐;使用输入验证就足够的例外情况。

当处理这种情况所需要的只是通知用户期望输入的内容时,定义和抛出InvalidUserInput异常毫无意义。

此外,应该注意用户输入预期在某些时候输入错误。在将来自外部世界的输入传递给程序内部之前验证输入是一种防御措施。

确定什么是特殊情况和什么不是什么有点困难。

答案 3 :(得分:6)

由于我通常使用Python编程,并且在该语言中,异常无处不在,对我而言,异常可能表示从系统错误到完全合法的条件。

例如,检查字符串是否包含整数的“pythonic”方法是尝试int(theString)并查看它是否引发异常。这是一个“特殊错误”吗?

同样,在Python中,for循环总是被认为是作用于迭代器,并且迭代器必须在完成其作业时引发'StopIteration'异常(for循环捕获该异常)。无论如何都是“特殊的”吗?

答案 4 :(得分:3)

我认为越接近实际,你就越不适合作为错误沟通方式的例外。在更高的抽象(例如Java或.net)中,异常可能会使错误消息传递给调用者。然而,这不是C中的情况。这也是一个框架vs api设计决策。

答案 5 :(得分:3)

如果你练习“告诉,不要问”,那么例外就是程序说“我做不到”的方式。它是“特殊的”,你说“做X”,它不能做X.一个简单的错误处理情况。在某些语言中,以这种方式工作是很常见的,在Java和C ++中,人们有其他意见,因为异常变得非常昂贵。

一般:例外只是意味着“我不能”

务实:......如果你有能力用你的语言工作。

公民身份:......并且您的团队允许。

答案 6 :(得分:2)

我认为应该使用例外来捕捉意外问题有几个很好的理由。

首先,他们创建了一个对象来封装异常,根据定义,它必须比处理简单的if语句要昂贵得多。作为Java示例,您应该调用File.exists()而不是常规地期望和处理FileNotFoundException。

其次,在当前方法之外(或者甚至是类)捕获的异常使得代码比在一个方法中处理完全更难读取。

话虽如此,我个人例外。它们使您无需明确处理所有可能发生但可能永远不会出现的类型错误,这会导致您重复写入错误 - 并且 - 非 - 返回 - 中止 - 每个方法调用的代码处理。

我的底线是......如果您可以合理地预期它会发生,那么它就是您应用程序的一部分,您应该为它编写代码。其他任何事情都是例外。

答案 7 :(得分:1)

我一直在想这个。 “特殊”是什么意思?也许没有严格的定义,但在给定的上下文中,我们可以使用任何经验法则来决定什么是特殊的吗?

例如,说“特殊”条件是违反职能合同的条件是否公平?

答案 8 :(得分:1)

以下是异常的定义:异常是在程序执行期间发生的事件,它会破坏程序指令的正常流程。

因此,回答你的问题,没有。例外情况适用于破坏性事件,这些事件可能是也可能不是例外。我喜欢这个定义,它很简单并且每次都有效 - 如果你像我一样购买异常。例如,用户提交不正确的un / pw,或者您有非法参数/错误的用户输入。在这里抛出一个例外是解决这些问题的最直接的方法,这些问题具有破坏性,但不是例外,甚至是未曾预料到的。

他们可能应该被称为中断,但那艘船已经航行了。

答案 9 :(得分:0)

KCwalina有一个观点。 最好找出代码失败的情况(达到极限)

我同意S.Lott的观点,有时验证比抛出异常更好。 话虽如此,OutOfMemory并不是您在应用程序中所期望的(除非它分配大量内存并且需要内存才能继续)。

我认为,这取决于应用程序的域名。

答案 10 :(得分:0)

Krzysztof Cwalina的声明有点误导。最初的陈述是指“特殊条件”,对我而言,我很自然地定义了什么是例外。尽管如此,我认为消息传递好了,因为我认为我们都在谈论'开发者'例外。

例外对于沟通非常有用,但是通过一些层次结构设计,它们对于某些关注点的分离也很有用,特别是在层之间(DAO,Business等)。当然,这仅在您以不同方式处理这些异常时才有用。

层次结构的一个很好的例子是spring的数据访问异常层次结构。

答案 11 :(得分:0)

我认为他是对的。看一下java中的数字解析。在解析之前,你甚至无法检查输入字符串。如果出现问题,您将被迫解析并检索NFE。解析失败是不是特别的?我想不。

答案 12 :(得分:0)

我当然相信只有在你有特殊情况时才应该使用例外。

麻烦在于“特殊”的定义。这是我的:

  

如果条件超出假设的正常范围,则该条件是例外   引发异常的系统部分的行为。

这有一些含义:

  • 例外取决于您的假设。如果函数假定它传递了有效参数,那么抛出IllegalArgumentException就可以了。但是,如果函数的契约表明它将以某种方式纠正输入中的输入错误,那么这种用法是“正常的”,它不应该对输入错误抛出异常。
  • 例外取决于子系统分层。如果网络被取消,网络IO功能肯定会引发异常,因为它假设有效连接。然而,基于ESB的消息代理应该处理丢弃的连接,因此如果它在内部使用这样的网络IO功能,那么它将需要适当地捕获和处理错误。如果不明显,try / catch实际上相当于一个子系统说“我的一个组件的特殊情况实际上被我认为是正常的,所以我需要处理它”。

答案 13 :(得分:0)

“Effective Java Second Edition”中使用了异常应该用于特殊情况的说法:最好的java书之一。

麻烦的是,这是脱离背景的。当作者声明异常应该是例外时,他刚刚展示了使用异常来终止while循环的一个例子 - 一个错误的异常使用。引用:

  顾名思义,

例外是   仅用于特殊条件;它们永远不应该用于普通的   控制流程。

所以这完全取决于你对“异常条件”的定义。脱离上下文,你可以暗示它应该很少被使用。

使用异常代替返回错误代码是好的,而使用它们来实现“聪明”或“更快”的技术并不好。这通常意味着“特殊情况”。

检查异常 - 不是错误的小错误,不应该停止执行。恩。 IO或文件解析 未经检查的异常 - 编程违反方法合同的“bug” - 例如。 OutOfBoundsException。或者一个错误,使继续执行成为一个非常糟糕的主意 - ex IO或文件解析一个非常重要的文件。也许是一个配置文件。

答案 14 :(得分:0)

它归结为需要什么工具来完成这项工作。

例外是一个非常强大的工具。在使用它们之前,请询问您是否需要此功能及其附带的复杂性。

异常可能看似简单,因为你知道当遇到异常的行时,一切都会停止。从这里发生了什么?

是否会发生未捕获的异常?

异常会被全局错误处理捕获吗?

异常是否会通过更多嵌套和详细的错误处理来处理?

你必须知道堆栈中的所有内容才能知道该异常会做什么。这违反了独立的概念。该方法现在依赖于错误处理来完成您期望的操作。

如果我有方法,我不应该关心那种方法之外的东西。我只关心输入是什么,如何处理它,以及如何返回响应。

当你使用异常时,你实际上是在说,我不在乎从这里发生了什么,出了什么问题,我不希望它变得更糟,做任何需要做的事情来缓解这个问题。

现在,如果您关心如何处理错误,您将进行更多思考并将其构建到方法的界面中,例如如果你试图找到某个对象可能会返回该对象的默认值,如果找不到该对象而不是抛出一些异常,例如“找不到对象”。

当你在方法界面中构建错误处理时,不仅该方法的签名更能描述它能做什么,而且它还负责如何处理方法调用者的错误。调用方法可能能够通过它工作,如果没有,它将再次报告链。最终,您将到达应用程序的入口点。现在抛出一个异常是合适的,因为如果你正在使用应用程序公共接口,你最好很好地理解如何处理异常。

让我举一个关于Web服务错误处理的例子。

级别1. global.asax中的全局错误处理 - 这是防止未捕获异常的安全网。绝不应该故意这样做。

级别2. Web服务方法 - 包含在try / catch中以保证它始终符合其json接口。

级别3.工作方法 - 这些方法获取数据,处理数据并将其原始返回到Web服务方法。

在worker方法中抛出异常是不对的。是的我有嵌套的Web服务方法错误处理,但该方法可以在其他可能不存在的地方使用。

相反,如果使用worker方法获取记录并且找不到记录,则只返回null。 Web服务方法检查响应,当它找到null时,它知道它无法继续。 Web服务方法知道它有错误处理来返回json所以抛出异常将只返回json中发生的事情的细节。从客户的角度来看,它很好地被打包成json,可以很容易地解析。

你看到每件作品都知道它需要做什么,做到了。当您在混合中抛出异常时,您会劫持应用程序流。这不仅导致难以遵循代码,而且滥用异常的响应是try / catch。现在你更有可能滥用另一个非常强大的工具。

我常常看到try / catch在应用程序中间捕获所有内容,因为开发人员害怕他们使用的方法比它看起来更复杂。