Are exceptions really for exceptional errors?
我的理解是,常识说只有在真正特殊的情况下才使用例外(事实上,我在这里已经看过好几次这种说法)。
然而,科瓦利纳说:
One of the biggest misconceptions about exceptions is that they are for"exceptional conditions." The reality is that they are for communicating error conditions. From a framework design perspective, there is no such thing as an"exceptional condition". Whether a condition is exceptional or not depends on the context of usage, --- but reusable libraries rarely know how they will be used. For example, OutOfMemoryException might be exceptional for a simple data entry application; it’s not so exceptional for applications doing their own memory management (e.g. SQL server). In other words, one man’s exceptional condition is another man’s chronic condition.
< /块引用>
他接着说,例外情况应该用于:
- 使用错误
- 程序错误
- 系统故障
考虑到科瓦利纳是微软CLR团队的项目经理,我问:你觉得他的声明怎么样?
这听起来过于简单,但我认为在适当的地方简单地使用异常是有意义的。在像Java和Python这样的语言中,异常非常常见,特别是在某些情况下。异常适用于希望通过代码路径冒泡并强制开发人员显式捕获的错误类型。在我自己的编码中,当错误不能被忽略,或者只是更优雅地抛出一个异常而不是返回一个错误值到函数调用等时,我会考虑适当的时间添加一个异常。
我可以马上想到一些最适合例外情况的地方:
- 不实施例外-非常合适的方式指定方法或函数不可用,而不是简单地不做就返回什么都行。
- 内存不足异常-很难想象更好的处理方法错误类型,因为它表示进程范围或操作系统范围的内存分配失败。当然,这是必须处理的!
- nullpointerException-访问一个空变量是一个程序员错误,IMO这是迫使错误冒泡到表面的另一个好地方。
- arrayIndexException-在C等不可原谅的语言中,缓冲区溢出是灾难性的。更好的语言可能返回某种类型的空值,或者一些实现,甚至环绕数组。在我看来,扔一个异常是一种更优雅的响应。
这绝不是一个全面的清单,但希望它能说明这一点。在优雅和合乎逻辑的地方使用异常。和编程一样,正确工作的正确工具是很好的建议。没有必要为了什么都不做而疯狂,但是完全忽略一个强大而优雅的工具也是同样不明智的。
对于编写框架的人来说,也许这很有趣。
对于我们其他人来说,这是令人困惑的(可能是无用的),对于普通的应用程序,例外情况必须被视为"例外"情况。异常会中断程序的正常顺序表示。
您应该谨慎地打破您的程序从上到下的普通顺序处理。异常处理——故意——很难理解。因此,为标准场景之外的事物保留例外。
示例:不要使用异常来验证用户输入。人们总是犯输入错误。这并不例外,这就是我们编写软件的原因。这就是if语句的用途。
当您的应用程序遇到内存不足的异常时,没有必要捕获它。这是个例外。"顺序执行"假设是不可能的。您的应用程序注定要失败,只是崩溃了,并希望您的RDBMS事务在崩溃之前完成。
确实很难知道到底是什么解释了一个"异常条件",它保证在程序中使用异常。
一个非常有助于交流错误原因的实例。正如Krzysztof cwalina所说:
One of the biggest misconceptions
about exceptions is that they are for
"exceptional conditions." The reality
is that they are for communicating
error conditions.为了给出一个具体的例子,假设我们有一个
getHeader(File f) 方法,它从一个文件中读取一些头并返回一个FileHeader 对象。尝试从磁盘读取数据时可能会出现几个问题。可能指定的文件不存在,文件包含无法读取的数据,意外的磁盘访问错误,内存不足等。具有多个失败方法意味着应该有多个报告错误的方法。
如果没有使用异常,但是需要用当前方法签名来传递发生的错误,我们所能做的最好的就是返回一个
null 。由于获取null 的信息量不大,因此我们从该结果中获得的最佳通信是"发生了某种错误,因此我们无法继续,抱歉。"——它无法通信错误的原因。(或者,我们也可以为fileheader对象提供类常量,这些对象指示fileNotFound条件等,模拟错误代码,但实际上有一种布尔类型与
TRUE 、FALSE 、FILE_NOT_FOUND 。如果我们得到一个
FileNotFound 或DeviceNotReady 异常(假设),至少我们知道错误的来源,如果这是一个最终用户应用程序,我们可以用解决问题的方法来处理错误。使用异常机制提供了一种通信方式,不需要使用错误代码来通知不在正常执行流中的条件。
然而,这并不意味着一切都应该由异常处理。正如S.Lott指出的:
Don't use exceptions to validate user
input, for example. People make
mistakes all the time. That's what
if-statements are for.这是一件压力不够大的事情。不知道何时确切地使用异常的一个危险是倾向于使用异常;在输入验证足够的地方使用异常。
在这种情况下,当需要处理的只是通知用户期望输入的内容时,定义和抛出
InvalidUserInput 异常是没有意义的。另外,应该注意的是,用户输入在某个时刻可能有错误的输入。在将外部世界的输入传递给程序内部之前,验证输入是一种防御措施。
决定什么是例外,什么不是例外有点困难。
因为我通常用Python编程,在这种语言中,异常无处不在,对我来说,异常可能代表从系统错误到完全合法的条件的任何东西。
例如,检查字符串是否包含整数的"pythonic"方法是尝试int(the string),并查看它是否引发异常。这是一个"例外错误"?
同样,在python中,for循环总是被认为是作用于迭代器的,迭代器在完成任务时必须引发"stopIteration"异常(for循环捕获该异常)。这是"例外"吗?
如果你练习"说,不要问",那么一个例外就是程序说"我不能这样做"。这是"例外",因为你说"做X",它不能做X。一个简单的错误处理情况。在某些语言中使用这种方法非常常见,在Java和C++中,人们有其他的观点,因为异常变得非常昂贵。
一般:例外只是指"我不能"
务实:…如果你能负担得起用你的语言那样工作。
公民身份:你的团队也同意。
我认为离地面越近,你就越不适合作为一种通讯方式的错误。在一个更高的抽象中,比如Java或.NET,一个异常可能会使一个优雅的方式传递错误消息给你的呼叫者。然而,在C中并不是这样。这也是一个框架与API设计决策。
我认为有几个很好的理由可以解释为什么应该使用异常来捕获意外的问题。
首先,它们创建一个对象来封装异常,根据定义,这必须使它比处理简单的if语句昂贵得多。作为一个Java示例,您应该调用Field.SimultSe(),而不是常规地期望和处理FieloToFunDestExpPosits。
其次,在当前方法(甚至是类)之外捕获的异常使代码比处理都在一个方法中更难读取。
尽管如此,我个人还是喜欢例外。它们消除了您显式处理所有可能发生的错误的需要,但可能永远不会出现类型错误,这会导致您重复地编写、打印错误,并在处理每个方法调用的非零返回代码时中止。
我的底线是…如果您可以合理地期望它发生,那么它是您的应用程序的一部分,您应该为此进行编码。其他的都是例外。
以下是异常的定义:异常是在程序执行期间发生的事件,它会中断程序指令的正常流。
因此,回答你的问题,不。例外是指破坏性事件,这可能是或可能不是例外。我喜欢这个定义,它很简单而且每次都有效-如果你像我一样接受例外。例如,用户提交了不正确的un/pw,或者您的参数非法/用户输入错误。在这里提出例外是解决这些问题最直接的方法,这些问题具有破坏性,但并不例外,甚至没有预料到。
他们可能应该被称为扰乱,但那艘船已经开航了。
我自己也一直在想这个。"例外"是什么意思?也许没有严格的定义,但是在给定的上下文中,有没有什么经验法则可以用来决定什么是特殊的?
例如,是否公平地说,"例外"条件是违反职能合同的条件?
归根结底,这项工作需要什么样的工具。
例外是一个非常强大的工具。在使用它们之前,先问问你是否需要这种能力以及随之而来的复杂性。
异常可能看起来很简单,因为您知道当异常行被击中时,所有的事情都会停止。但是从这里发生了什么?
是否会发生未捕获的异常?
全局错误处理是否会捕获异常?
异常将由更嵌套和详细的错误处理来处理吗?
您必须知道堆栈中的所有内容,才能知道该异常将做什么。这违背了独立的概念。该方法现在依赖于错误处理来完成您期望的工作。
如果我有一个方法,我不应该关心该方法之外的内容。我只关心输入是什么,如何处理它,以及如何返回响应。
当你使用一个例外时,你基本上是在说,我不在乎从这里发生了什么,出了什么问题,我不希望它变得更糟,做任何需要做的事情来缓解这个问题。
现在,如果您关心如何处理错误,您将进行更多的思考,并将其构建到方法的接口中,例如,如果您试图查找某个对象,如果找不到该对象,则可能返回该对象的默认值,而不是抛出一些异常,如"找不到对象"。
当您在方法接口中构建错误处理时,不仅该方法的签名更能描述它可以做什么,而且它还将如何处理错误的责任放在方法的调用方上。调用者方法是否可以通过它工作,如果不能,它将在链上再次报告。最终,您将到达应用程序的入口点。现在,抛出一个异常是合适的,因为如果您使用应用程序公共接口,那么您更好地了解如何处理异常。
让我给您举一个Web服务的错误处理示例。
1级。global.asax中的全局错误处理——这是防止未捕获异常的安全网。这不应该是有意的。
2级。Web服务方法-包装在try/catch中,以确保它始终符合其JSON接口。
3级。辅助方法-这些方法获取数据、处理数据并将其原始返回到Web服务方法。
在worker方法中,引发异常是不正确的。是的,我有嵌套的Web服务方法错误处理,但是该方法可以在其他可能不存在的地方使用。
相反,如果使用worker方法获取记录但找不到该记录,则它只返回空值。Web服务方法检查响应,当它发现空值时,它知道它无法继续。Web服务方法知道它有返回JSON的错误处理,因此引发异常只会返回JSON中发生的详细信息。从客户机的角度来看,将它打包到可以轻松解析的JSON中是非常好的。
你看到每一件事,就知道它需要做什么,然后去做。当您在混合中抛出异常时,您将劫持应用程序流。这不仅导致代码难以遵循,而且对滥用异常的响应是try/catch。现在你更可能滥用另一个非常强大的工具。
我经常看到一个try/catch在一个应用程序的中间捕获所有的东西,因为开发人员害怕他们使用的方法比它看起来更复杂。
在"有效的Java第二版"中使用异常是例外情况的说法:最好的Java书籍之一。
问题是这是断章取义。当作者声明异常应该是异常的时候,他刚刚展示了一个使用异常终止while循环的例子——一个错误的异常使用。引述:
exceptions are, as their name implies, to
be used only for exceptional conditions; they should never be used for ordinary
control flow.所以这完全取决于你对"异常条件"的定义。脱离上下文,你可以暗示它应该很少被使用。
使用异常代替返回的错误代码是好的,而使用它们来实现"聪明"或"更快"的技术则不是好的。这通常就是"特殊情况"的意思。
已检查异常-不是错误且不应停止执行的小错误。例如IO或文件解析未选中的异常-不符合方法约定的编程"bug"-例如outofboundsException。或者是一个错误,使得继续执行是一个非常糟糕的想法——比如IO或文件解析一个非常重要的文件。可能是配置文件。
我相信只有当你有一个特殊的条件时才应该使用例外。
问题在"例外"的定义中。这是我的:
A condition is exceptional if it is outside the assumed normal
behaviour of the part of the system that raises the exception.这有一些含义:
- 例外取决于你的假设。如果函数假定传递了有效参数,则可以引发IllegalArgumentException。但是,如果函数的约定说它将以某种方式更正输入中的输入错误,那么这种用法是"正常的",它不应该对输入错误抛出异常。
- 异常取决于子系统分层。如果网络不被推荐,网络IO功能当然会引发异常,因为它假定一个有效的连接。但是,基于ESB的消息代理应该处理掉的连接,因此如果它在内部使用这样的网络IO函数,那么它将需要适当地捕获和处理错误。如果不明显,则Try/Catch实际上相当于一个子系统,它说"我的某个组件的异常情况实际上被我认为是正常的,所以我需要处理它"。
我认为他是对的。看看Java中的数字解析。在解析之前甚至不能检查输入字符串。如果出现问题,您将被迫解析和检索NFE。解析失败是异常的吗?我想没有。
科瓦利纳的声明有点误导人。最初的陈述是指"例外情况",对我来说,很自然地,我就是那个定义例外或不例外的人。不过,我认为消息是通过OK传递的,因为我认为我们都在讨论"开发人员"异常。
异常对于通信来说是很好的,但是有了一点层次结构设计,它们对于一些关注点的分离也很好,特别是在层(DAO、业务等)之间。当然,只有当您以不同的方式处理这些异常时,这才有用。
层次结构的一个很好的例子是Spring的数据访问异常层次结构。
科瓦利纳说得有道理。识别代码将失效的情况将是很好的(达到极限)
我同意S.Lott的观点,有时候验证比抛出异常要好。尽管如此,在您的应用程序中,内存不足并不是您所期望的(除非它正在分配一个大的内存,并且需要内存继续分配)。
我认为,这取决于应用程序的领域。