为什么应该保守地使用例外?
可能重复:
为什么exception处理不好?
我经常看到/听到有人说exception应该很less使用,但永远不能解释为什么。 虽然这可能是正确的,但理由通常是一种滑稽的说法: “这被称为一个例外” ,对我来说,这似乎是一位应该得到尊敬的程序员/工程师永远不会接受的那种解释。
有一个例外可以用来解决的一系列问题。 为什么把它们用于控制stream是不明智的? 对于如何使用它们而言是非常保守的哲学是什么? 语义? 性能? 复杂? 美学? 惯例?
我以前看过一些关于绩效的分析,但是在一些与某些系统相关而与别人无关的层面上。
再次,我不一定不同意他们应该为特殊情况而得救,但我想知道共识的基本原理是什么(如果存在的话)。
摩擦的主要点是语义。 许多开发人员滥用例外,并在每个机会抛出。 这个想法是使用例外情况。 例如,错误的用户input不会被视为exception,因为您预期这会发生并为此做好准备。 但是如果你试图创build一个文件而磁盘上没有足够的空间,那么是的,这是一个明显的例外。
另一个问题是例外情况经常被抛出并被吞噬。 开发人员使用这种技术来简单地“沉默”程序,并尽可能长时间地运行,直到完全崩溃。 这是非常错误的。 如果你不处理exception,如果你没有适当的反应,释放一些资源,如果你没有loggingexception发生或至less不通知用户,那么你没有使用exception的意思。
直接回答你的问题。 例外应该很less使用,因为例外情况很less,例外情况很贵。
很less见,因为您不希望您的程序在每次按下button或每个格式错误的用户input时都崩溃。 说数据库可能突然不能访问,磁盘上可能没有足够的空间,你所依赖的一些第三方服务是脱机的,这一切都可能发生,但很less,这是明显的例外情况。
昂贵,因为抛出exception会中断正常的程序stream程。 运行时将展开堆栈,直到find可处理exception的适当exception处理程序。 它还会收集呼叫信息,一直传递给处理程序将收到的exception对象。 这一切都有成本。
这并不是说,使用例外(微笑)不能有例外。 有时候,如果抛出一个exception,而不是通过很多层转发返回码,它可以简化代码结构。 作为一个简单的规则,如果你期望经常调用某种方法,并在一半的时间内发现一些“特殊”的情况,那么最好find另一种解决scheme。 但是,如果您期望大部分时间正常的操作stream程,而这种“特殊”情况只能在极less数情况下出现,那么抛出exception就可以了。
@Comments:exception可以在一些不太特殊的情况下使用,如果这可以使你的代码更简单和容易。 这个选项是开放的,但我认为它在实践中相当罕见。
为什么把它们用于控制stream是不明智的?
因为例外会中断正常的“控制stream”。 您引发exception,程序的正常执行被放弃,可能会使对象处于不一致的状态,并且某些打开的资源不一致。 当然,C#具有使用语句,它将确保对象将被丢弃,即使从使用体中抛出exception也是如此。 但是让我们从语言中抽象出来。 假设框架不会为你处理对象。 你手动做。 你有一些如何请求和释放资源和内存的系统。 您在全系统范围内有协议,负责在什么情况下释放对象和资源。 你有规则如何处理外部库。 如果程序遵循正常的操作stream程,那么效果很好。 但突然间在执行过程中,你会抛出exception。 其中一半的资源没有兑现。 有一半还没有被要求。 如果这项行动目前是交易性的,那么它就被打破了。 您的资源处理规则将不起作用,因为那些负责释放资源的代码部分将不会执行。 如果其他人想要使用这些资源,他们可能会发现它们处于不一致的状态并崩溃,因为它们无法预测这种特殊情况。
比方说,你想要一个方法M()调用方法N()做一些工作,并安排一些资源,然后返回给M(),它将使用它,然后处置它。 精细。 现在N()中出现了一些错误,并且它引发了一个你在M()中没有预料到的exception,所以这个exception一直出现在顶端,直到它被C中的某个方法所捕获,这个方法将不知道底层发生了什么在N()中是否以及如何释放一些资源。
抛出exception,你创build一个方法,使你的程序进入许多难以预测,理解和处理的新的不可预知的中间状态。 这有点类似于使用GOTO。 devise一个可以从一个位置随机跳转到另一个位置的程序是非常困难的。 维护和debugging也很困难。 当程序变得越来越复杂时,你就会忽略什么时候什么地方发生什么事情来修复它。
虽然“在特殊情况下抛出exception”是glib的答案,但实际上可以定义这些情况是什么: 当前提条件满足时,但后置条件不能得到满足 。 这样可以在不牺牲error handling的情况下编写更严格,更紧密,更有用的后置条件; 否则,无一例外,您必须更改后置条件以允许每个可能的错误状态。
- 在调用函数之前, 前提条件必须是真实的。
- 后置条件是函数返回 后保证的。
- exception安全说明exception如何影响函数或数据结构的内部一致性,并经常处理从外部传入的行为(例如函子,模板参数等等)。
构造函数
对于每一个可能用C ++编写的类,每个构造函数都没有什么可说的,但是有一些东西。 其中最主要的是构造对象(即构造函数返回的构造对象)将被破坏。 你不能修改这个后置条件,因为语言假定它是真实的,并且会自动调用析构函数。 (从技术上讲,你可以接受未定义行为的可能性,对于这种行为,语言对任何事情 都不作任何保证,但是在其他地方可能更好。
当构造函数不能成功时抛出exception的唯一select是修改该类的基本定义(“类不variables”),以允许有效的“null”或僵尸状态,从而允许构造函数通过构build僵尸来“成功” 。
僵尸的例子
这个僵尸修改的一个例子是std :: ifstream ,你必须经常检查它的状态,然后才能使用它。 因为std :: string没有,所以你总是保证你可以在施工后立即使用它。 想象一下,如果你必须编写这样的代码,而且如果你忘了检查僵尸状态,你会不知不觉地得到不正确的结果或者破坏程序的其他部分:
string s = "abc"; if (s.memory_allocation_succeeded()) { do_something_with(s); // etc. }
即使命名该方法是一个很好的例子,你如何修改类的不变和界面的情况下string既不能预测也不能自己处理。
validationinput示例
我们来举个常见的例子:validation用户input。 仅仅因为我们想要允许失败的input并不意味着parsing函数需要在其后置条件中包含它。 这意味着我们的处理程序需要检查parsing器是否失败,但是。
// boost::lexical_cast<int>() is the parsing function here void show_square() { using namespace std; assert(cin); // precondition for show_square() cout << "Enter a number: "; string line; if (!getline(cin, line)) { // EOF on cin // error handling omitted, that EOF will not be reached is considered // part of the precondition for this function for the sake of example // // note: the below Python version throws an EOFError from raw_input // in this case, and handling this situation is the only difference // between the two } int n; try { n = boost::lexical_cast<int>(line); // lexical_cast returns an int // if line == "abc", it obviously cannot meet that postcondition } catch (boost::bad_lexical_cast&) { cout << "I can't do that, Dave.\n"; return; } cout << n * n << '\n'; }
不幸的是,这显示了两个C ++范围如何要求你打破RAII / SBRM的例子。 在Python中没有这个问题,并显示我希望C ++有一个例子 – 尝试其他:
# int() is the parsing "function" here def show_square(): line = raw_input("Enter a number: ") # same precondition as above # however, here raw_input will throw an exception instead of us # using assert try: n = int(line) except ValueError: print "I can't do that, Dave." else: print n * n
前提条件
先决条件不一定要被检查 – 违反一个总是表示一个逻辑失败,他们是来电者的责任 – 但如果你确实检查他们,然后抛出一个exception是适当的。 (在某些情况下,返回垃圾或使程序崩溃更为合适,但在其他情况下,这些操作可能是非常错误的。如何最好地处理未定义的行为是另一个话题。
特别是,对照stdlibexception层次结构的std :: logic_error和std :: runtime_error分支。 前者往往被用于违反先决条件,而后者则更适合违反后置条件。
- 昂贵
内核调用(或其他系统API调用)来pipe理内核(系统)信号接口 - 很难分析
goto
语句的许多问题都适用于exception。 他们经常在多个例程和源文件中跳过潜在的大量代码。 阅读中间源代码并不总是显而易见的。 (这是在Java中) - 中间代码并不总是被预期的
被跳过的代码可能已经或可能没有被写入,并且有可能出现exception退出。 如果原来是这样写的,那么可能就没有考虑到这一点。 认为:内存泄漏,文件描述符泄漏,套接字泄漏,谁知道? - 维护并发症
维护处理exception跳转的代码很困难。
抛出exception在某种程度上类似于goto语句。 这样做是为了控制stream量,并以不可理解的意大利面代码结束。 更糟糕的是,在某些情况下,你甚至不知道跳到哪里(也就是说,如果你没有在给定的上下文中捕捉exception)。 这公然违反了增强可维护性的“最小惊喜”原则。
例外情况使得难以推断你的程序状态。 以C ++为例,你必须做额外的思考,以确保你的函数是非常强大的exception安全的,比你不需要做的时候要做的事情要多。
原因是没有例外,函数调用可以返回,也可以先终止程序。 有了exception,函数调用可以返回,也可以终止程序,也可以跳转到某个catch块。 所以你不能仅仅通过查看前面的代码来跟踪控制stream程。 你需要知道被调用的函数是否可以抛出。 您可能需要知道可以抛出哪些东西以及抓到哪些东西,这取决于您是在关心控制权在哪里,或者只关心它离开了当前范围。
出于这个原因,人们说“除非情况真是非常之外,不要使用例外”。 当你真正意识到这一点时,“非常特别”就意味着“出现了一些情况,处理错误的回报价值的成本超过了成本”。 所以是的,这是一个空洞的声明,虽然一旦你有一些“非常特别”的直觉,这是一个很好的经验法则。 当人们谈论stream量控制时,他们意味着在本地推理的能力(不参考捕获块)是返回值的好处。
Java比“C ++”有更广泛的“非常例外”的定义。 C ++程序员比Java程序员更希望查看函数的返回值,因此在Java中“非常特殊”可能意味着“我不能返回一个非null对象作为这个函数的结果”。 在C ++中,更可能的意思是“我非常怀疑我的调用者可以继续”。 所以,如果Javastream不能读取文件,则会抛出Javastream,而C ++stream(默认情况下)会返回一个表示错误的值。 不过在所有情况下,这都是你愿意强迫你的调用者编写的代码的问题。 所以这确实是一个编码风格的问题:你必须达成你的代码应该是什么样子的共识,以及你想写多less“错误检查”代码来对付你想要做的“exception安全”推理。
所有语言的广泛共识似乎是这样做的最好的方式是错误可能的恢复的方式(因为不可恢复的错误导致没有例外的代码,但仍然需要一个检查和返回你自己的错误,代码中使用错误返回的错误)。 所以人们期望“我称之为这个function抛出exception”意味着“ 我不能继续”,而不仅仅是“不能继续”。 这不是固有的例外,这只是一个习惯,但像任何一个良好的编程习惯,这是一个聪明的人谁曾经尝试过的方式,而不是喜欢的结果提倡的习惯。 我也有不好的经验抛出太多例外。 因此,我个人认为,除非有关情况使例外情况特别具有吸引力,否则我认为这是“非常特殊的”。
顺便说一句,除了推理你的代码的状态,还有性能影响。 例外情况现在通常很便宜,在您有权关心performance的语言中。 他们可以比多层次的“哦,结果是一个错误,我最好退出自己的错误太多,然后”更快。 在过去的糟糕时代,真正的担心是抛出一个exception,抓住它,继续下一件事,会让你做得太慢而无用。 所以在这种情况下,“非常特殊”的意思是“情况糟透了,可怕的performance已经不重要了”。 现在已经不是这样了(尽pipe在一个紧密的循环中的例外仍然是显而易见的),并希望说明为什么“真正例外”的定义需要灵活。
真的没有共识。 整个问题有些主观,因为抛出exception的“恰当性”常常是由语言本身的标准库内的现有实践所提出的。 与标准的Java标准库相比,C ++标准库抛出exception的频率要低得多,即使对于预期的错误,例如无效的用户input(例如Scanner.nextInt
),它几乎总是会优先考虑exception。 我相信这对于开发者意见何时适合抛出exception有很大的影响。
作为一名C ++程序员,我个人更喜欢为非常“例外”的情况保留例外,例如内存不足,磁盘空间不足,发生的启示等等。但我并不坚持认为这是绝对正确的方法的东西。
我不认为,这个例外应该很less使用。 但。
并非所有的团队和项目都准备好使用例外。 例外的使用要求程序员具有高度的资格,特殊的工艺和缺乏大的遗留的非exception安全的代码。 如果你有很大的代码库,那么它几乎总是不是exception安全的。 我相信你不想重写它。
如果你打算广泛使用exception,那么:
- 准备好教你们的人们安全是什么例外
- 你不应该使用原始的内存pipe理
- 广泛使用RAII
另一方面,在有强大团队的新项目中使用例外可能会使代码变得更清洁,更容易维护,甚至更快:
- 你不会错过或忽略错误
- 你不必写这个返回代码的检查,而实际上也不知道如何处理低级别的错误代码
- 当你被迫编写exception安全的代码时,它变得更加结构化
编辑11/20/2009 :
我刚刚阅读了MSDN关于提高托pipe代码性能的文章 ,这部分提醒了我这个问题:
抛出exception的性能成本是显着的。 尽pipe结构化的exception处理是处理错误条件的推荐方法,但要确保只在发生错误情况的特殊情况下使用exception。 不要将例外用于常规控制stream。
当然,这只适用于.NET,而且还专门针对那些开发高性能应用程序(如我自己)的应用程序。 所以这显然不是一个普遍的事实。 尽pipe如此,我们还有很多.NET开发人员,所以我觉得值得注意。
编辑 :
好,首先,让我们弄清楚一件事:我不打算在性能问题上与任何人吵架。 总的来说,事实上,我倾向于认同那些认为过早优化是罪过的人。 不过,我只想提两点:
-
海报要求客观的理由背后的传统观点,例外应谨慎使用。 我们可以讨论所有我们想要的可读性和适当的devise; 但是这些都是人们随时准备争论的主观问题。 我认为海报知道这一点。 事实是,使用exception来控制程序stream程往往是一种低效的做事方式。 不,并不总是 ,但经常 。 这就是为什么要谨慎使用例外的合理build议,就好像build议less吃红酒或less喝葡萄酒一样。
-
优化没有很好的理由和编写高效的代码是有区别的。 这样做的必然结果是,如果不是优化的话,写一些稳健的东西和一些简单的低效率的东西是有区别的。 有时我认为,当人们争论exception处理这些事情时,他们实际上只是在讨论彼此,因为他们正在讨论根本不同的事情。
为了说明我的观点,请考虑以下C#代码示例。
示例1:检测无效的用户input
这是我所谓的exception滥用的一个例子。
int value = -1; string input = GetInput(); bool inputChecksOut = false; while (!inputChecksOut) { try { value = int.Parse(input); inputChecksOut = true; } catch (FormatException) { input = GetInput(); } }
这个代码对我来说是荒谬的。 当然,它的作品 。 没有人与此争论。 但它应该是这样的:
int value = -1; string input = GetInput(); while (!int.TryParse(input, out value)) { input = GetInput(); }
例2:检查文件的存在
我认为这种情况其实很常见。 由于它处理文件I / O,对于很多人来说,它似乎更加“可以接受”:
string text = null; string path = GetInput(); bool inputChecksOut = false; while (!inputChecksOut) { try { using (FileStream fs = new FileStream(path, FileMode.Open)) { using (StreamReader sr = new StreamReader(fs)) { text = sr.ReadToEnd(); } } inputChecksOut = true; } catch (FileNotFoundException) { path = GetInput(); } }
这似乎很合理,对吗? 我们正在尝试打开一个文件; 如果它不在那里,我们捕获这个exception,并尝试打开一个不同的文件…有什么不对?
真的没什么。 但是考虑一下这个select,它不会抛出任何例外:
string text = null; string path = GetInput(); while (!File.Exists(path)) path = GetInput(); using (FileStream fs = new FileStream(path, FileMode.Open)) { using (StreamReader sr = new StreamReader(fs)) { text = sr.ReadToEnd(); } }
当然,如果这两种方法的performance实际上是相同的,那么这纯粹是一个理论问题。 那么,让我们来看看。 对于第一个代码示例,我创build了一个10000个随机string的列表,其中没有一个代表正确的整数,然后在最后添加了一个有效的整数string。 使用上述两种方法,这些都是我的结果:
使用try
/ catch
块:25.455 秒
使用int.TryParse
:1.637 毫秒
对于第二个例子,我做了基本相同的事情:制作了一个10000个随机string的列表,其中没有一个是有效的path,然后在最后添加了一个有效的path。 这是结果:
使用try
/ catch
块:29.989 秒
使用File.Exists
:22.820 毫秒
很多人会对此作出回应,说:“是的,投掷和捕捉10000个例外是非常不切实际的,这夸大了结果。” 当然是的。 抛出一个exception和处理错误的input之间的区别不会被用户注意到。 事实上,在这两种情况下,使用exception的速度比可读的替代方法慢1000到10000多倍 – 如果不是这样的话。
这就是为什么我在下面包含GetNine()
方法的例子。 这不是它无法忍受的慢或无法接受的缓慢; 这是比它应该是慢的… 没有很好的理由 。
再次,这只是两个例子。 当然,有些时候,使用exception的性能打击不是这么严重(Pavel的权利,毕竟这取决于实施)。 我只是说:让我们面对事实,大家 – 在上面的例子中,抛出和捕捉exception类似于GetNine()
; 这只是一个低效的做法,可以做的更好 。
你是在寻求一个理由,就好像这是每个人都不知道为什么而跳上一个大潮的情况之一。 但实际上答案是显而易见的,我想你已经知道了。 exception处理有可怕的performance。
好吧,对于特定的业务场景来说 ,也许没问题,但是相对而言 ,抛出/捕获exception会导致比许多情况下更多的开销。 你知道,我知道: 大多数时候 ,如果你使用exception来控制程序stream,你只是写慢代码。
你可能会问:为什么这个代码不好?
private int GetNine() { for (int i = 0; i < 10; i++) { if (i == 9) return i; } }
我敢打赌,如果你分析这个function,你会发现它对于典型的业务应用程序执行得相当快。 这并没有改变这样一个事实,即这是一个可以做得更好的事情,是一种非常低效的方式。
当人们谈论exception“滥用”时,这就是人们的意思。
关于例外的所有经验规则都归结为主观条款。 你不应该期望得到什么时候使用它们,什么时候不使用的定义。 “只有在特殊情况下”。 良好的通告定义:例外情况是特殊情况。
何时使用exception与“我怎么知道这个代码是一个还是两个? 这部分是一个风格问题,部分是偏好。 例外是一个工具。 他们可以被使用和滥用,find两者之间的界限是编程艺术和技巧的一部分。
有很多的意见,并作出权衡。 find对你说话的东西,并遵循它。
这并不是说例外应该很less使用。 只是在特殊情况下才应该抛出。 例如,如果用户input了错误的密码,那也不例外。
原因很简单:exception突然退出函数,并将堆栈传播到catch
块。 这个过程在计算上是非常昂贵的:C ++构build它的exception系统对于“正常”的函数调用没有太多的开销,所以当引发exception时,必须做很多工作才能find去的地方。 而且,由于每一行代码都可能引发exception。 如果我们有一些经常引发exception的函数f
,那么我们现在必须在每次调用f
使用我们的try
/ catch
块。 这是一个非常糟糕的接口/实现耦合。
我在一篇关于C ++exception的文章中提到过这个问题。
相关部分:
几乎总是使用exception来影响“正常”stream程是一个坏主意。 正如我们在3.1节中已经讨论的那样,exception生成了不可见的代码path。 如果仅在error handling场景中执行这些代码path,则这些代码path是可以接受的。 但是,如果我们将exception用于任何其他目的,我们的“正常”代码执行被分成可见和不可见部分,这使得代码很难阅读,理解和扩展。
My approach to error handling is that there are three fundamental types of errors:
- An odd situation that can be handled at the error site. This might be if a user inputs an invalid input at a command line prompt. The correct behavior is simply to complain to the user and loop in this case. Another situation might be a divide-by-zero. These situations aren't really error situations, and are usually caused by faulty input.
- A situation like the previous kind, but one that can't be handled at the error site. For instance, if you have a function that takes a filename and parses the file with that name, it might not be able to open the file. In this case, it can't deal with the error. This is when exceptions shine. Rather than use the C approach (return an invalid value as a flag and set a global error variable to indicate the problem), the code can instead throw an exception. The calling code will then be able to deal with the exception – for instance to prompt the user for another filename.
- A situation that Should Not Happen. This is when a class invariant is violated, or a function receives an invalid paramter or the like. This indicates a logic failure within the code. Depending on the level of failure, an exception may be appropriate, or forcing immediate termination may be preferable (as
assert
does). Generally, these situations indicate that something has broken somewhere in the code, and you effectively cannot trust anything else to be correct – there may be rampant memory corruption. Your ship is sinking, get off.
To paraphrase, exceptions are for when you have a problem you can deal with, but you can't deal with at the place you notice it. Problems you can't deal with should simply kill the program; problems you can deal with right away should simply be dealt with.
I read some of the answers here. I'm still amazed on what all this confusion is about. I strongly disagree with all this exceptions==spagetty code. With confusion I mean, that there are people, which don't appreciate C++ exception handling. I'm not certain how I learned about C++ exception handling — but I understood the implications within minutes. This was around 1996 and I was using the borland C++ compiler for OS/2. I never had a problem to decide, when to use exceptions. I usually wrap fallible do-undo actions into C++ classes. Such do-undo actions include:
- creating/destroying a system handle (for files, memory maps, WIN32 GUI handles, sockets, and so on)
- setting/unsetting handlers
- allocating/deallocating memory
- claiming/releasing a mutex
- incrementing/decrementing a reference count
- showing/hiding a window
Than there are functional wrappers. To wrap system calls (which do not fall into the former category) into C++. Eg read/write from/to a file. If something fails, an exception will be thrown, which contains full information about the error.
Then there is catching/rethrowing exceptions to add more information to a failure.
Overall C++ exception handling leads to more clean code. The amount of code is drasticly reduced. Finally one can use a constructor to allocate fallible resources and still maintain a corruption free environment after such a failure.
One can chain such classes into complex classes. Once a constructor of some member/base object is exectued, one can rely on that all other constructors of the same object (executed before) executed successfully.
Exceptions are a very unusual method of flow control compared to the traditional constructs (loops, ifs, functions, etc.) The normal control flow constructs (loops, ifs, function calls, etc.) can handle all the normal situations. If you find yourself reaching for an exception for a routine occurrence, then perhaps you need to consider how your code is structured.
But there are certain types of errors that cannot be handled easy with the normal constructs. Catastrophic failures (like resource allocation failure) can be detected at a low level but probably can't be handled there, so a simple if-statement is inadequate. These types of failures generally need to be handled at a much higher level (eg, save the file, log the error, quit). Trying to report an error like this through traditional methods (like return values) is tedious and error-prone. Furthermore, it injects overhead into layers of mid-level APIs to handle this bizarre, unusual failure. The overhead distracts client of these APIs and requires them to worry about issues that are beyond their control. Exceptions provide a way to do non-local handling for big errors that's mostly invisible to all the layers between the detection of the problem and the handler for it.
If a client calls ParseInt
with a string, and the string doesn't contain an integer, then the immediate caller probably cares about the error and knows what to do about it. So you'd design ParseInt to return a failure code for something like that.
On the other hand, if ParseInt
fails because it couldn't allocate a buffer because memory is horribly fragmented, then the caller isn't going to know what to do about that. It would have to bubble this unusual error up and up to some layer that deals with these fundamental failures. That taxes everyone in between (because they have to accommodate the error passing mechanism in their own APIs). An exception makes it possible to skip over those layers (while still ensuring necessary clean-up happens).
When you're writing low-level code, it can be hard to decide when to use traditional methods and when to throw exceptions. The low-level code has to make the decision (throw or not). But it's the higher level code that truly knows what's expected and what's exceptional.
There's several reasons in C++.
First, it's frequently hard to see where exceptions are coming from (since they can be thrown from almost anything) and so the catch block is something of a COME FROM statement. It's worse than a GO TO, since in a GO TO you know where you're coming from (the statement, not some random function call) and where you're going (the label). They're basically a potentially resource-safe version of C's setjmp() and longjmp(), and nobody wants to use those.
Second, C++ doesn't have garbage collection built in, so C++ classes that own resources get rid of them in their destructors. Therefore, in C++ exception handling the system has to run all the destructors in scope. In languages with GC and no real constructors, like Java, throwing exceptions is a lot less burdensome.
Third, the C++ community, including Bjarne Stroustrup and the Standards Committee and various compiler writers, has been assuming that exceptions should be exceptional. In general, it's not worth going against language culture. The implementations are based on the assumption that exceptions will be rare. The better books treat exceptions as exceptional. Good source code uses few exceptions. Good C++ developers treat exceptions as exceptional. To go against that, you'd want a good reason, and all the reasons I see are on the side of keeping them exceptional.
This is a bad example of using exceptions as control flow:
int getTotalIncome(int incomeType) { int totalIncome= 0; try { totalIncome= calculateIncomeAsTypeA(); } catch (IncorrectIncomeTypeException& e) { totalIncome= calculateIncomeAsTypeB(); } return totalIncome; }
Which is very bad, but you should be writing:
int getTotalIncome(int incomeType) { int totalIncome= 0; if (incomeType == A) { totalIncome= calculateIncomeAsTypeA(); } else if (incomeType == B) { totalIncome= calculateIncomeAsTypeB(); } return totalIncome; }
This second example obviously needs some refactoring (like using the design pattern strategy), but illustrates well that exceptions are not meant for control flow.
Exceptions also have some performance penalties associated, but performance problems should follow the rule: "premature optimization is the root of all evil"
- Maintainability: As mentioned by people above, throwing exceptions at a drop of a hat is akin to using gotos.
- Interoperability: You can't interface C++ libraries with C/Python modules (atleast not easily) if you are using exceptions.
- Performance degradation: RTTI is used to actually find the type of the exception which imposes additional overhead. Thus exceptions are not suitable for handling commonly occurring use cases(user entered int instead of string etc).
I would say that exceptions are a mechanism to get you out of current context (out of current stack frame in the simplest sense, but it's more than that) in a safe way. It's the closest thing structured programming got to a goto. To use exceptions in the way they were intended to be used, you have to have a situation when you can't continue what you're doing now, and you can't handle it at the point where you are now. So, for example, when user's password is wrong, you can continue by returning false. But if the UI subsystem reports that it can't even prompt the user, simply returning "login failed" would be wrong. The current level of code simply does not know what to do. So it uses an exception mechanism to delegate the responsibility to someone above who may know what to do.
One very practical reason is that when debugging a program I often flip on First Chance Exceptions (Debug -> Exceptions) to debug an application. If there are a lot of exceptions happening it's very difficult to find where something has gone "wrong".
Also, it leads to some anti-patterns like the infamous "catch throw" and obfuscates the real problems. For more information on that see a blog post I made on the subject.
I prefer to use exceptions as little as possible. Exceptions force the developer to handle some condition that may or may not be a real error. The definition of whether the exception in question is a fatal problem or a problem that must be handled immediately.
The counter argument to that is it just requires lazy people to type more in order to shoot themselves in their feet.
Google's coding policy says to never use exceptions , especially in C++. Your application either isn't prepared to handle exceptions or it is. If it isn't, then the exception will probably propagate it up until your application dies.
It's never fun to find out some library you have used throws exceptions and you were not prepared to handle them.
Legitimate case to throw an exception:
- You try to open a file, it's not there, a FileNotFoundException is thrown;
Illegitimate case:
- You want to do something only if a file doesn't exist, you try to open the file, and then add some code to the catch block.
I use exceptions when I want to break the flow of the application up to a certain point . This point is where the catch(…) for that exception is. For example, it's very common that we have to process a load of projects, and each project should be processed independently of the others. So the loop that process the projects has a try…catch block, and if some exception is thrown during the project processing, everything is rolled back for that project, the error is logged, and the next project is processed. Life goes on.
I think you should use exceptions for things like a file that doesn't exist, an expression that is invalid, and similar stuff. You should not use exceptions for range testing/ data type testing/ file existence/ whatever else if there's an easy/ cheap alternative to it. You should not use exceptions for range testing/ data type testing/ file existence/ whatever else if there's an easy/ cheap alternative to it because this sort of logic makes the code hard to understand:
RecordIterator<MyObject> ri = createRecordIterator(); try { MyObject myobject = ri.next(); } catch(NoSuchElement exception) { // Object doesn't exist, will create it }
This would be better:
RecordIterator<MyObject> ri = createRecordIterator(); if (ri.hasNext()) { // It exists! MyObject myobject = ri.next(); } else { // Object doesn't exist, will create it }
COMMENT ADDED TO THE ANSWER:
Maybe my example wasn't very good – the ri.next() should not throw an exception in the second example, and if it does, there's something really exceptional and some other action should be taken somewhere else. When the example 1 is heavily used, developers will catch a generic exception instead of the specific one and assume that the exception is due to the error that they're expecting, but it can be due to something else. In the end, this leads to real exceptions being ignored as exceptions became part of the application flow, and not an exception to it.
The comments on this may add more than my answer itself.
Basically, exceptions are an unstructured and hard to understand form of flow control. This is necessary when dealing with error conditions that are not part of the normal program flow, to avoid having error handling logic clutter up the normal flow control of your code too much.
IMHO exceptions should be used when you want to provide a sane default in case the caller neglects to write error handling code, or if the error might best be handled further up the call stack than the immediate caller. The sane default is to exit the program with a reasonable diagnostic error message. The insane alternative is that the program limps along in an erroneous state and crashes or silently produces bad output at some later, harder to diagnose point. If the "error" is enough a normal part of program flow that the caller could not reasonably forget to check for it, then exceptions should not be used.
I think, "use it rarely" ist not the right sentence. I would prefer "throw only in exceptional situations".
Many have explained, why exceptions should not used in normal situations. Exceptions have their right for error handling and purely for error handling.
I will focus on an other point:
An other thing is the performance issue. Compilers struggled long to get them fast. I am not sure, how the exact state is now, but when you use exceptions for control flow, than you will get an other trouble: Your program will become slow!
The reason is, that exceptions are not only very mighty goto-statements, they also have to unwind the stack for all the frames they leave. Thus implicitely also the objects on stack have to be deconstructed and so on. So without be aware of it, one single throw of an exception will really get a whole bunch of mechanics be involved. The processor will have to do a mighty lot.
So you will end up, elegantly burning your processor without knowing.
So: use exceptions only in exceptional cases — Meaning: When real errors occured!
The purpose of exceptions is to make software fault tolerant. However having to provide a response to every exception thrown by a function leads to suppression. Exceptions are just a formal structure forcing programmers to acknowledge that certain things can go wrong with a routine and that the client programmer needs to be aware of these conditions and cater for them as necessary.
To be honest, exceptions are a kludge added to programming languages to provide developers with some formal requirement that shifts the responsibility of handling error cases from the immediate developer to some future developer.
I believe that a good programming language does not support exceptions as we know them in C++ and Java. You should opt for programming languages that can provide alternative flow for all sorts of return values from functions. The programmer should be responsible for anticipating all forms of outputs of a routine and handle them in a seperate code file if I could have my way.
I use exceptions if:
- an error occured that cannot be recovered from locally AND
- if the error is not recovered from the program should terminate.
If the error can be recovered from (the user entered "apple" instead of a number) then recover (ask for the input again, change to default value, etc.).
If the error cannot be recovered from locally but the application can continue (the user tried to open a file but the file does not exist) then an error code is appropriate.
If the error cannot be recovered from locally and the application cannot continue without recovering (you are out of memory/disk space/etc.), then an exception is the right way to go.
Who said they should be used conservatively ? Just never use exceptions for flow control and thats it. And who cares about the cost of exception when it already thrown ?
我的两分钱:
I like to use exceptions, because it allows me to program as if no errors will happen. So my code remains readable, not scattered with all kinds of error-handling. Of course, the error handling (exception handling) is moved to the end (the catch block) or is considered the responsability of the calling level.
A great example for me, is either file handling, or database handling. Presume everything is ok, and close your file at the end or if some exception occurs. Or rollback your transaction when an exception occurred.
The problem with exceptions, is that it quickly gets very verbose. While it was meant to allow your code to remain very readable, and just focus on the normal flow of things, but if used consistently almost every function call needs to be wrapped in a try/catch block, and it starts to defeat the purpose.
For a ParseInt as mentioned before, i like the idea of exceptions. Just return the value. If the parameter was not parseable, throw an exception. It makes your code cleaner on the one hand. At the calling level, you need to do something like
try { b = ParseInt(some_read_string); } catch (ParseIntException &e) { // use some default value instead b = 0; }
The code is clean. When i get ParseInt like this scattered all over, i make wrapper functions that handle the exceptions and return me default values. 例如
int ParseIntWithDefault(String stringToConvert, int default_value=0) { int result = default_value; try { result = ParseInt(stringToConvert); } catch (ParseIntException &e) {} return result; }
So to conclude: what i missed througout the discussion was the fact that exceptions allow me to make my code easier/more readable because i can ignore the error conditions more. Problems:
- the exceptions still need to be handled somewhere. Extra problem: c++ does not have the syntax that allows it to specify which exceptions a function might throw (like java does). So the calling level is not aware which exceptions might need to be handled.
- sometimes code can get very verbose, if every function needs to be wrapped in a try/catch block. But sometimes this still makes sense.
So that makes it hard to find a good balance sometimes.
I'm sorry but the answer is "they are called exceptions for a reason." That explanation is a "rule of thumb". You can't give a complete set of circumstances under which exceptions should or should not be used because what a fatal exception (English definition) is for one problem domain is normal operating procedure for a different problem domain. Rules of thumb are not designed to be followed blindly. Instead they are designed to guide your investigation of a solution. "They are called exceptions for a reason" tells you that you should determine ahead of time what is a normal error the caller can handle and what is an unusual circumstance the caller cannot handle without special coding (catch blocks).
Just about every rule of programming is really a guideline saying "Don't do this unless you have a really good reason": "Never use goto", "Avoid global variables", "Regular expressions pre-increment your number of problems by one", etc. Exceptions are no exception….