为什么exception处理不好?
Google的Go语言没有任何例外的deviseselect,而Linux的名声也叫做exception废话。 为什么?
例外情况使写入代码的代码非常容易,在这种情况下抛出的exception将会破坏不variables并使对象处于不一致的状态。 他们基本上强迫你记住,你所做的每一个陈述都可能抛出,并正确处理。 这样做可能会非常棘手和违反直觉。
考虑这样一个简单的例子:
class Frobber { private: int m_NumberOfFrobs; FrobManager m_FrobManager; public: void Frob() { m_NumberOfFrobs++; m_FrobManager.HandleFrob(new FrobObject()); } };
假设FrobManager
将delete
FrobObject
,这看起来好,对不对? 或者可能不是…想象一下,如果FrobManager::HandleFrob()
或operator new
引发exception。 在这个例子中, m_NumberOfFrobs
的增量不会回滚。 因此,使用Frobber
这个实例的任何人都会有可能被破坏的对象。
这个例子看起来很愚蠢(好吧,我不得不自己稍微构build一个:-)),但是,如果一个程序员没有经常考虑exception,并确保每个状态变换都被滚动只要有投掷,你就会遇到麻烦。
作为一个例子,你可以把它想象成你想到的互斥体。 在关键部分内部,您需要依靠几条语句来确保数据结构不被破坏,而其他线程无法看到您的中间值。 如果这些陈述中的任何一个都是随机的,那么你最终会处于一个痛苦的世界。 现在拿走锁和并发,思考每个方法。 如果您愿意的话,可以将每种方法看作是对象状态排列的一个事务。 在你的方法调用开始时,对象应该是干净的状态,最后还应该有一个干净的状态。 在这之间,variablesfoo
可能与bar
不一致,但是你的代码最终会纠正这个问题。 什么例外意味着你的任何一个陈述可以在任何时候打断你。 在每个单独的方法中,都有责任使你得到正确的结果,并在发生这种情况时回滚,或者命令你的操作不会影响对象状态。 如果你弄错了(这种错误很容易),那么调用者就会看到你的中间值。
像C ++程序员喜欢提到的RAII这样的方法是解决这个问题的最终方法。 但他们不是银弹。 它会确保你释放资源,但不会让你不必考虑对象状态和调用者看到中间值的错误。 所以,对很多人来说,通过编码风格来说, 没有例外 。 如果你限制你写的代码的种类,那么引入这些错误就更困难了。 如果你不这样做,那么犯错很容易。
整本书都是用C ++写的关于exception安全编码的。 很多专家都搞错了。 如果它真的很复杂,有很多细微之处,那么这可能是一个好兆头,你需要忽略这个function。 🙂
Go语言devise常见问题解释了Go没有例外的原因:
例外是一个类似的故事。 已经提出了许多针对例外的devise,但是每个都增加了语言和运行时间的复杂性。 从本质上来说,例外涵盖了function,甚至是门厅; 他们有广泛的影响。 也有人担心他们对图书馆会有什么影响。 根据定义,它们是支持它们的其他语言的非凡体验,表明它们对库和接口规范有着深远的影响。 如果能find一个能使他们真正成为卓越的devise,而不鼓励常见的错误转变成需要每个程序员进行补偿的特殊控制stream程,那将是一件好事。
像generics一样,例外仍然是一个悬而未决的问题。
换句话说,他们还没有想出如何在Go中支持例外,他们认为这是令人满意的。 他们并不是说例外本身就是坏的;
更新 – 2012年5月
Go的devise师们现在已经从栅栏上爬下来了。 他们的FAQ现在说这个:
我们认为,在try-catch-finally成语中,将exception与控制结构耦合,会导致代码错综复杂。 它也倾向于鼓励程序员标记太多的普通错误,比如没有打开一个文件,例外。
去采取不同的方法。 对于普通的error handling,Go的多值返回可以轻松地报告错误而不会重载返回值。 规范的错误types,加上Go的其他function,使error handling愉快,但与其他语言的差异很大。
Go也有一些内置的function来发出信号并从真正的特殊条件中恢复。 恢复机制仅作为函数状态的一部分被执行,在错误发生之后被拆除,这足以处理灾难,但不需要额外的控制结构,并且如果使用得当,可以产生干净的error handling代码。
有关详细信息,请参阅延迟,恐慌和恢复文章。
所以简短的回答是,他们可以用多值回报来做不同的事情。 (而且它们确实有一个exception处理的forms。)
…和Linux的名声的Linus叫做exception垃圾。
如果你想知道为什么莱纳斯认为例外是废话,最好的办法就是寻找他关于这个话题的着作。 到目前为止,我唯一追踪的是这个引用embedded在C ++的几封电子邮件中 :
“整个C ++exception处理的事情从根本上被打破了,特别是内核的破坏。
你会注意到他特别在讨论C ++exception,而不是一般的exception。 (而C ++exception显然有一些问题,使他们很难正确使用。)
我的结论是,Linus没有把exception(一般来说)称为“废话”!
例外情况本身并不糟糕,但是如果你知道他们会发生很多事情,他们在性能上可能会很昂贵。
经验法则是exception应该标记exception的条件,并且你不应该使用它们来控制程序stream程。
我不同意“只在特殊情况下抛出exception”。 一般来说,这是误导。 exception是错误条件(执行失败)。
无论您使用何种语言,都可以拿起一份“ 框架devise指南 :约定,习语和可重用.NET库模式(第二版)”。 抛出exception的章节没有同行。 第一版中的一些引用(第二版在我的工作中):
- 不要返回错误代码。
- 错误代码很容易被忽略,而且往往是。
- 例外是报告框架中的错误的主要手段。
- 一个好的经验法则是,如果一个方法没有做它的名字,它应该被认为是一个方法级别的失败,导致一个exception。
- 如果可能的话, 不要在正常的控制stream程中使用exception。
关于exception的好处(API一致性,error handling代码的位置的select,改进的健壮性等等)有很多说明。关于性能的章节包括几种模式(Tester-Doer,Try-Parse)。
exception和exception处理并不差。 像任何其他function一样,它们可能会被滥用。
从golang的angular度来看,我认为没有exception处理可以使编译过程简单而安全。
从Linus的angular度来看,我知道内核代码是关于angular落案例的。 所以拒绝例外是有道理的。
在代码中,例外情况是合理的,可以将当前任务放在地板上,而且常见的情况代码比error handling更重要。 但是他们需要从编译器生成代码。
例如,在大多数高级的面向用户的代码(如Web和桌面应用程序代码)中它们都很好。
典型的观点是,没有办法知道哪些exception会从一段特定的代码中出来(取决于语言),而且它们太像goto
,这使得很难在心理上追踪执行。
http://www.joelonsoftware.com/items/2003/10/13.html
这个问题肯定没有共识。 我想说,从像Linus这样的硬核C程序员的angular度来看,exception无疑是一个坏主意。 不过,一个典型的Java程序员却处于截然不同的状态。
它们本身的例外情况并非“不好”,有时例外情况往往是不好的。 在处理exception时可以应用几个指导原则来帮助缓解这些问题。 其中一些包括(但肯定不限于):
- 不要使用exception来控制程序stream – 即不要依赖“catch”语句来改变逻辑stream。 这不仅会隐藏逻辑中的各种细节,还会导致性能下降。
- 当返回的“状态”更有意义时,不要在函数内抛出exception – 只能在特殊情况下抛出exception。 创buildexception是一个代价高昂的性能密集型操作。 例如,如果调用一个方法来打开一个文件并且该文件不存在,则抛出“FileNotFound”exception。 如果调用确定客户帐户是否存在的方法,则返回一个布尔值,不要返回“CustomerNotFound”exception。
- 在确定是否处理exception时,除非可以对exception做一些有用的事情,否则不要使用“try … catch”子句。 如果你不能处理这个exception,你应该让它冒出调用堆栈。 否则,exception可能会被处理程序“吞噬”,细节将会丢失(除非重新抛出exception)。
exception并不坏。 它们适合C ++的RAII模型,这是关于C ++的最优雅的东西。 如果你已经有一堆代码不是exception安全的,那么在这种情况下它们是不好的。 如果你正在编写非常低级的软件,比如说linux操作系统,那么它们就是坏的。 如果你喜欢用一堆错误返回检查来抛弃你的代码,那么它们就没有帮助。 如果在抛出exception(C ++析构函数提供)时没有资源控制计划,那么它们就不好用了。
因此,例外的一个很好的用例就是……
假设你在一个项目中,每个控制器(大约20个不同的主控器)通过一个动作方法扩展了一个超类控制器。 然后每一个控制器都做了一堆不同的东西,在一种情况下调用对象B,C,D,在另一种情况下调用对象F,G,D。 在很多情况下,如果有大量的返回代码,每个控制器都有不同的处理方式,这里就有例外。 我打了所有的代码,从“D”扔了适当的exception,在超类控制器的行动方法,现在我们所有的控制器是一致的。 以前,D对于多个不同的错误情况返回null,我们想告诉最终用户,但是不能,我不想将StreamResponse变成一个讨厌的ErrorOrStreamResponse对象(在我看来,混合错误的数据结构是一个难闻的气味,我看到很多代码返回一个“stream”或其他types的实体与embedded在它的错误信息(它应该真的是该函数返回成功的结构或错误结构,我可以做exception与返回代码)….虽然多重响应的C#方式是我可能会考虑的,但在很多情况下,exception可能会跳过很多层(我不需要清理资源的层)。
是的,我们不得不担心每个级别和任何资源清理/泄漏,但总的来说,我们的控制器之后没有任何资源需要清理。
感谢上帝,我们有例外,或者我会在一个巨大的重构,浪费太多时间在一些应该是一个简单的编程问题。
理论上他们真的很糟糕。 在完美的math世界中,你不能得到exception情况。 看function性语言,它们没有任何副作用,所以它们实际上不具备普通情况下的来源。
但是,现实是另一回事。 我们总是有“意外”的情况。 这就是我们需要例外的原因。
我认为我们可以把exceptionSituationObserver的语法糖看作exception。 你只是得到例外的通知。 而已。
随着围棋,我想他们会介绍一些将处理“意外”情况。 我可以猜测,他们会试图使它听起来更具有破坏性,作为例外,更像是应用程序逻辑。 但这只是我的猜测。
C ++的exception处理模式(构成了Java的部分基础)又反过来引入了一些好的概念,但也有一些严重的局限性。 exception处理的关键devise意图之一是允许方法确保它们将满足其后置条件或抛出exception,并确保在方法可以退出之前需要发生的任何清理将会发生。 不幸的是,C ++,Java和.net的exception处理模式都不能提供任何好的方法来处理意外因素阻止执行预期清理的情况。 这反过来意味着,如果出现意想不到的事情(C ++处理exception的方法发生在堆栈展开过程中),所有人都必须承担一切都会骤然停止的风险,接受由于发生的问题而无法解决的情况在堆栈展开清理过程中,会被误认为是可以解决的(可能已经清理成功了),或者接受这样的可能性:堆栈展开清理触发了一个通常可以parsing的exception的无法parsing的问题,因为处理后一个问题的代码没有被注意到,所以它被“解决”了。
即使exception处理通常是好的,但认为不可接受的exception处理模式并不是不合理的,因为这种exception处理模式不能为处理其他问题之后清理出现的问题提供一个好的方法。 这并不是说一个框架不能用一个exception处理的范例来devise,即使在多重失败的情况下也可以确保合理的行为,但是没有一个顶级语言或框架可以这样做。
我没有读过所有其他的答案,所以这个可能已经被提及,但是有一个批评是他们导致程序打破了长链,使得在debugging代码时很难追查错误。 例如,如果Foo()调用Bar(),它调用调用ToString()的Wah(),然后将错误的数据推入ToString(),结果看起来像Foo()中的错误,这是一个几乎完全不相关的函数。
- 没有被处理的例外通常是不好的。
- 处理不好的exception是不好的(当然)。
- exception处理的“优劣”取决于上下文/范围和适当性,而不是为了这样做。
好的,无聊的答案在这里。 我想这实际上取决于语言。 如果一个例外可以将分配的资源留在后面,应该避免。 在脚本语言中,他们只是抛弃或跳过应用程序stream的一部分。 这本身是令人不快的,但是用例外来逃避几乎致命的错误是一个可以接受的想法。
对于错误信号,我通常更喜欢错误信号。 所有都取决于API,用例和严重性,或者如果日志满足。 此外,我试图重新定义行为,并throw Phonebooks()
。 这个想法是“例外”往往是死胡同,但“电话簿”包含错误恢复或替代执行路线的有用信息。 (没有find一个好的用例,但继续尝试。)
对我来说这个问题很简单。 许多程序员不适当地使用exception处理程序。 更多的语言资源更好。 有能力处理exception是好的。 使用不当的一个例子是一个值必须是整数而不是被validation的,或者另一个input可能被划分而不被检查以进行零除…exception处理可能是避免更多工作和难以思考的简单方法,程序员可能要做一个肮脏的快捷方式,并应用exception处理……如果algorithm处理的某些问题由于其本质而不确定,那么声明:“专业代码永不失败”可能是虚幻的。 也许在未知的情况下自然而然地发挥exception处理器的作用。 良好的编程实践是一个有争议的问题。