32

我的理解是,常识只在真正异常的情况下使用异常(事实上,我在 SO 上已经多次看到该声明)。

然而,Krzysztof Cwalina 说:

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

然后他还继续说例外应该用于:

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

考虑到 Krzysztof Cwalina 是 MS CLR 团队的 PM,我问:您如何看待他的声明?

4

15 回答 15

26

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

我能想到的一些最适合例外的地方:

  • NotImplementedException - 指定特定方法或函数不可用的非常合适的方式,而不是简单地返回而不做任何事情。
  • OutOfMemory异常 - 很难想象有更好的方法来处理此类错误,因为它表示进程范围或操作系统范围的内存分配失败。当然,这是必须处理的!
  • NullPointerException - 访问空变量是程序员的错误,IMO 这是另一个迫使错误浮出水面的好地方
  • ArrayIndexException - 在像 C 这样无情的语言中,缓冲区溢出是灾难性的。更好的语言可能会返回某种类型的空值,或者在某些实现中,甚至会环绕数组。在我看来,抛出异常是一种更优雅的响应。

这绝不是一个完整的列表,但希望它说明了这一点。在优雅和合乎逻辑的地方使用异常。与编程一样,正确工作的正确工具是很好的建议。毫无意义地异常疯狂是没有意义的,但是完全忽略一个强大而优雅的工具同样是不明智的。

于 2008-10-08T00:41:59.583 回答
11

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

对于我们其他人来说,这很令人困惑(并且可能毫无用处)。对于普通应用程序,必须将异常作为“异常”情况搁置一旁。异常会中断程序的正常顺序呈现。

您应该小心打破程序的普通自上而下的顺序处理。异常处理 - 故意 - 难以阅读。因此,为标准场景之外的事情保留例外。

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

当您的应用程序出现 OutOfMemory 异常时,捕获它是没有意义的。这很特别。“顺序执行”的假设是不可能的。您的应用程序注定要崩溃,并希望您的 RDBMS 事务在您崩溃之前完成。

于 2008-10-08T00:25:04.077 回答
9

确实很难知道究竟是什么解释了一个“异常条件”,它保证在程序中使用异常。

一个对使用交流错误原因非常有帮助的实例。正如 Krzysztof Cwalina 的引述所说:

关于异常的最大误解之一是它们是针对“异常情况”的。实际情况是它们用于传达错误条件。

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

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

如果没有使用异常,但需要使用当前方法签名传达发生的错误类型,我们能做的最好的事情就是返回一个null. 由于获得的null信息量不是很大,因此我们从该结果中获得的最佳沟通是“发生了某种错误,所以我们无法继续,抱歉。” -- 它没有传达错误的原因。

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

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

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

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

例如,不要使用异常来验证用户输入。人们总是犯错误。这就是 if 语句的用途。

这是一件不能强调的事情。不知道何时确切使用异常的危险之一是倾向于对异常感到高兴。使用输入验证就足够的异常。

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

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

决定什么是特殊的,什么不是特殊的有点困难。

于 2008-10-08T01:02:05.190 回答
6

由于我通常使用 Python 进行编程,并且在该语言中异常无处不在,因此对我来说,异常可能代表从系统错误到完全合法条件的任何内容。

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

同样,在 Python 中,for 循环总是被认为作用于迭代器,迭代器在完成其工作时必须引发“StopIteration”异常(for 循环捕获该异常)。无论如何,这是“例外”吗?

于 2008-10-08T00:47:48.480 回答
3

我认为你越接近地面,作为错误通信手段的异常就越不合适。在 Java 或 .net 等更高的抽象中,异常可能是一种将错误消息传递给调用者的优雅方式。然而,在 C 中情况并非如此。这也是一个框架与 api 设计决策。

于 2008-10-08T00:54:12.553 回答
3

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

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

务实的:......如果你能负担得起用你的语言那样工作的话。

公民身份:......而且你的团队允许这样做。

于 2008-10-31T14:57:42.097 回答
2

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

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

其次,在当前方法(甚至可能是类)之外捕获的异常使代码比处理全部在一个方法中更难阅读。

话虽如此,我个人喜欢例外。它们使您无需显式处理所有这些可能发生但可能永远不会键入的错误,这会导致您重复编写 print-an-error-and-abort-on-non-zero-return-每个方法调用的代码处理。

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

于 2008-10-08T01:03:10.397 回答
2

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

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

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

于 2015-07-23T15:40:01.920 回答
1

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

例如,说“异常”条件是违反函数契约的条件是否公平?

于 2008-10-08T00:35:46.910 回答
0

KCwalina 有一个观点。识别代码失败的情况会很好(达到限制)

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

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

于 2008-10-08T00:37:45.847 回答
0

Krzysztof Cwalina 的声明有点误导。最初的陈述是指“例外情况”,对我来说,我自然是定义什么是例外的人。尽管如此,我认为消息通过了 OK,因为我认为我们都在谈论“开发人员”异常。

异常非常适合通信,但通过一些层次结构设计,它们也非常适合一些关注点分离,特别是在层之间(DAO、业务等)。当然,这仅在您以不同方式处理这些异常时才有用。

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

于 2008-10-09T14:50:47.077 回答
0

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

于 2011-05-25T21:41:20.227 回答
0

我当然相信只有在您有异常情况时才应使用异常。

问题在于“异常”的定义。这是我的:

如果条件超出了引发异常的系统部分的假定正常行为,则该条件是异常的。

这有一些含义:

  • 异常取决于你的假设。如果一个函数假定它传递了有效的参数,那么抛出一个 IllegalArgumentException 是可以的。但是,如果一个函数的合约说它将以某种方式纠正输入中的输入错误,那么这种用法是“正常的”,它不应该在输入错误时抛出异常。
  • 异常取决于子系统分层。如果网络被取消注释,网络 IO 函数肯定会引发异常,因为它假定连接有效。然而,基于 ESB 的消息代理将被期望处理断开的连接,因此如果它在内部使用这样的网络 IO 功能,那么它需要适当地捕获和处理错误。如果不明显,try/catch 实际上相当于一个子系统,它说“我的一个组件的异常情况实际上被我认为是正常的,所以我需要处理它”。
于 2012-03-15T12:09:47.407 回答
0

“Effective Java Second Edition”中使用了例外情况应该使用异常的说法:最好的Java书籍之一。

问题在于,这是断章取义的。当作者声明异常应该是异常时,他刚刚展示了一个使用异常来终止 while 循环的示例 - 一个糟糕的异常使用。去引用:

例外,顾名思义,仅用于例外情况;它们不应该用于普通的控制流。

所以这一切都取决于你对“异常条件”的定义。断章取义,您可以暗示它应该很少使用。

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

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

于 2015-06-10T17:36:28.670 回答
0

归根结底是完成这项工作需要什么工具。

异常是一个非常强大的工具。在使用它们之前,请询问您是否需要这种功能以及随之而来的复杂性。

异常可能看起来很简单,因为您知道当遇到异常的行时,一切都会停止。但是从这里会发生什么?

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

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

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

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

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

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

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

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

让我举一个例子来说明我对 Web 服务的错​​误处理。

级别 1. global.asax 中的全局错误处理 - 这是防止未捕获异常的安全网。永远不应该故意达到这一点。

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

第 3 级。工作方法 - 这些方法获取数据、处理数据并将其原始返回给 Web 服务方法。

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

相反,如果使用 worker 方法获取记录并且找不到记录,它只会返回 null。Web 服务方法检查响应,当它发现 null 时,它知道它不能继续。Web 服务方法知道它有返回 json 的错误处理,因此抛出异常只会在 json 中返回所发生事件的详细信息。从客户的角度来看,它被打包成易于解析的 json 非常棒。

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

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

于 2015-06-25T21:28:35.033 回答