Possible Duplicate:
Why is exception handling bad?可能重复:为什么异常处理不好?
I often see/hear people say that exceptions should only be used rarely, but never explain why. While that may be true, rationale is normally a glib: "it's called an exception for a reason" which, to me, seems to be the sort of explanation that should never be accepted by a respectable programmer/engineer.
我经常听到人们说,例外应该很少使用,但从不解释为什么。虽然这可能是对的,但基本原理通常是一个小问题:“它被称为一个理由的例外”,在我看来,这似乎是一种永远不应该被一个受人尊敬的程序员/工程师接受的解释。
There is a range of problems that an exception can be used to solve. Why is it unwise to use them for control flow? What is the philosophy behind being exceptionally conservative with how they are used? Semantics? Performance? Complexity? Aesthetics? Convention?
异常可以用来解决一系列问题。为什么将它们用于控制流是不明智的?在使用方法上格外保守背后的哲学是什么?语义?性能?复杂性?美学?公约?
I've seen some analysis on performance before, but at a level that would be relevant to some systems and irrelevant to others.
我以前见过一些关于性能的分析,但是在与某些系统相关而与其他系统无关的层面上。
Again, I don't necessarily disagree that they should be saved for special circumstances, but I'm wondering what the consensus rationale is (if such a thing exists).
同样,我并不一定不同意应该将它们保存到特殊情况下,但我想知道共识的基本原理是什么(如果存在这样的东西)。
28 个解决方案
#1
88
The primary point of friction is semantics. Many developers abuse exceptions and throw them at every opportunity. The idea is to use exception for somewhat exceptional situation. For example, wrong user input does not count as an exception because you expect this to happen and ready for that. But if you tried to create a file and there was not enough space on disk, then yes, this is a definite exception.
摩擦的主要点是语义。许多开发人员滥用异常并抓住每一个机会抛出异常。我们的想法是在异常情况下使用异常。例如,错误的用户输入并不能算作一个异常,因为您希望发生这种情况并为此做好准备。但是,如果您尝试创建一个文件,而磁盘上没有足够的空间,那么是的,这是一个明确的例外。
One other issue is that exceptions are often thrown and swallowed. Developers use this technique to simply "silence" the program and let it run as long as possible until completely collapsing. This is very wrong. If you don't process exceptions, if you don't react appropriately by freeing some resources, if you don't log the exception occurrence or at least not notify the user, then you're not using exception for what they are meant.
另一个问题是,异常常常被抛出和吞噬。开发人员使用这种技术来简单地“屏蔽”程序,并让它尽可能长时间地运行,直到完全崩溃为止。这是非常错误的。如果您不处理异常,如果您没有通过释放一些资源来进行适当的响应,如果您没有记录异常发生,或者至少没有通知用户,那么您就没有为它们的含义使用异常。
Answering directly your question. Exceptions should rarely be used because exceptional situations are rare and exceptions are expensive.
直接回答你的问题。不应该使用异常,因为异常情况很少,并且异常代价高昂。
Rare, because you don't expect your program crash at every button press or at every malformed user input. Say, database may suddenly not be accessible, there may not be enough space on disk, some third party service you depend on is offline, this all can happen, but quite rarely, these would be clear exceptional cases.
很少,因为你不会期望你的程序在每一个按钮按下或每一个错误的用户输入时崩溃。比方说,数据库可能突然无法访问,磁盘上可能没有足够的空间,你依赖的第三方服务是离线的,这一切都可能发生,但很少,这些都是很明显的例外情况。
Expensive, because throwing an exception will interrupt the normal program flow. The runtime will unwind the stack until it finds an appropriate exception handler that can handle the exception. It will also gather the call information all along the way to be passed to the exception object the handler will receive. It all has costs.
代价高昂,因为抛出异常将中断正常的程序流。运行时将展开堆栈,直到找到可以处理异常的适当异常处理程序。它还将收集调用信息,一直传递到处理程序将接收的异常对象。它都有成本。
This is not to say that there can be no exception to using exceptions (smile). Sometimes it can simplify the code structure if you throw an exception instead of forwarding return codes via many layers. As a simple rule, if you expect some method to be called often and discover some "exceptional" situation half the time then it is better to find another solution. If however you expect normal flow of operation most of the time while this "exceptional" situation can only emerge in some rare circumstances, then it is just fine to throw an exception.
这并不是说使用异常(smile)没有例外。有时,如果抛出异常而不是通过多个层转发返回代码,则可以简化代码结构。作为一个简单的规则,如果您期望某些方法经常被调用,并且偶尔发现一些“异常”情况,那么最好找到另一个解决方案。但是,如果您期望大多数情况下正常的操作流程,而这种“异常”情况只能在一些罕见的情况下出现,那么抛出一个异常就可以了。
@Comments: Exception can definitely be used in some less-exceptional situations if that could make your code simpler and easier. This option is open but I'd say it comes quite rare in practice.
@Comments:在一些不太特殊的情况下可以使用异常,这样可以使代码更简单、更简单。这个选项是开放的,但我得说它在实践中非常罕见。
Why is it unwise to use them for control flow?
为什么将它们用于控制流是不明智的?
Because exceptions disrupt normal "control flow". You raise an exception and normal execution of the program is abandoned potentially leaving objects in inconsistent state and some open resources unfreed. Sure, C# has the using statement which will make sure the object will be disposed even if an exception is thrown from the using body. But let us abstract for the moment from the language. Suppose the framework won't dispose objects for you. You do it manually. You have some system for how to request and free resources and memory. You have agreement system-wide who is responsible for freeing objects and resources in what situations. You have rules how to deal with external libraries. It works great if the program follows the normal operation flow. But suddenly in the middle of execution you throw an exception. Half of the resources are left unfreed. Half have not been requested yet. If the operation was meant to be transactional now it is broken. Your rules for handling resources will not work because those code parts responsible for freeing resources simply won't execute. If anybody else wanted to use those resources they may find them in inconsistent state and crash as well because they could not predict this particular situation.
因为异常会破坏正常的“控制流”。您将引发一个异常,程序的正常执行将被放弃,可能会使对象处于不一致的状态,并使一些开放资源无法释放。当然,c#有使用语句,它将确保即使从使用主体抛出异常,也会对该对象进行处理。但是让我们暂时从语言中抽象出来。假设框架不会为您配置对象。你做手工。您有一些关于如何请求和释放资源和内存的系统。您在系统范围内拥有协议,负责在什么情况下释放对象和资源。您有如何处理外部库的规则。如果程序遵循正常的操作流程,那么它会工作得很好。但是突然在执行过程中你抛出了一个异常。一半的资源没有被释放。其中一半还没有被要求。如果操作本来是事务性的,现在它被破坏了。您处理资源的规则不会起作用,因为负责释放资源的代码部分不会执行。如果其他人想要使用这些资源他们可能会发现它们处于不一致的状态并且崩溃,因为他们无法预测这种特殊情况。
Say, you wanted a method M() call method N() to do some work and arrange for some resource then return it back to M() which will use it and then dispose it. Fine. Now something goes wrong in N() and it throws an exception you didn't expect in M() so the exception bubbles to the top until it maybe gets caught in some method C() which will have no idea what was happening deep down in N() and whether and how to free some resources.
例如,您希望一个方法M()调用方法N()来做一些工作并安排一些资源,然后将它返回给M(), M()将使用它,然后处理它。很好。现在出现问题在N()抛出一个异常你没想到在M()所以除了泡沫顶部,直到它也许会陷入一些C()方法将不知道发生了什么在N()和内心深处是否以及如何免费的一些资源。
With throwing exceptions you create a way to bring your program into many new unpredictable intermediate states which are hard to prognose, understand and deal with. It's somewhat similar to using GOTO. It is very hard to design a program that can randomly jump its execution from one location to the other. It will also be hard to maintain and debug it. When the program grows in complexity, you just going to lose an overview of what when and where is happening less to fix it.
除了抛出异常之外,您还创建了一种方法,使您的程序进入许多难以预测的中间状态,这些状态很难探测、理解和处理。它有点类似于使用后藤。要设计一个程序来随机地从一个位置跳到另一个位置是非常困难的。它也很难维护和调试。当程序变得复杂的时候,你就会忽略什么时候和什么地方发生的事情更少去修复它。
#2
60
While "throw exceptions in exceptional cirumstances" is the glib answer, you can actually define what those circumstances are: when preconditions are satisfied, but postconditions cannot be satisfied. This allows you to write stricter, tighter, and more useful postconditions without sacrificing error-handling; otherwise, without exceptions, you have to change the postcondition to allow for every possible error state.
虽然“异常情况下的抛出异常”是一个简短的回答,但您实际上可以定义这些情况是什么:当满足了先决条件,但不能满足后置条件。这允许您在不牺牲错误处理的情况下编写更严格、更严格和更有用的后置条件;否则,您必须更改postcondition,以允许每个可能的错误状态。
- Preconditions must be true before calling a function.
- 在调用函数之前,先决条件必须为真。
- Postcondition is what the function guarantees after it returns.
- 后置条件是函数在返回后所保证的。
- Exception safety states how exceptions affect the internal consistency of a function or data structure, and often deal with behavior passed in from outside (e.g. functor, ctor of a template parameter, etc.).
- 异常安全性说明异常如何影响函数或数据结构的内部一致性,并经常处理从外部传入的行为(例如:函子、模板参数的ctor等)。
Constructors
There's very little you can say about every constructor for every class that could possibly be written in C++, but there are a few things. Chief among them is that constructed objects (i.e. for which the constructor succeeded by returning) will be destructed. You cannot modify this postcondition because the language assumes it is true, and will call destructors automatically. (Technically you can accept the possibility of undefined behavior for which the language makes no guarantees about anything, but that is probably better covered elsewhere.)
对于每一个可以用c++编写的类,你几乎可以说不出每一个构造函数,但是有一些东西。其中最主要的是构造对象(即构造函数成功返回的对象)将被销毁。您不能修改这个后置条件,因为语言假定它为真,并将自动调用析构函数。(从技术上讲,您可以接受语言对任何事情都没有保证的不确定行为的可能性,但这在其他地方可能更好。)
The only alternative to throwing an exception when a constructor cannot succeed is to modify the basic definition of the class (the "class invariant") to allow valid "null" or zombie states and thus allow the constructor to "succeed" by constructing a zombie.
在构造函数不能成功时抛出异常的唯一替代方法是修改类的基本定义(“类不变量”),以允许有效的“空”或“僵尸”状态,从而允许构造函数通过构造一个僵尸来“成功”。
Zombie example
An example of this zombie modification is std::ifstream, and you must always check its state before you can use it. Because std::string, for example, doesn't, you are always guaranteed that you can use it immediately after construction. Imagine if you had to write code such as this example, and if you forgot to check for the zombie state, you'd either silently get incorrect results or corrupt other parts of your program:
这种僵尸修改的一个例子是std::ifstream,在使用它之前必须始终检查它的状态。因为std::string,例如,没有,您总是被保证在构建之后可以立即使用它。想象一下,如果您必须编写这样的代码,并且如果您忘记检查僵尸状态,那么您要么默默地得到错误的结果,要么破坏程序的其他部分:
string s = "abc";
if (s.memory_allocation_succeeded()) {
do_something_with(s); // etc.
}
Even naming that method is a good example of how you must modify the class' invariant and interface for a situation string can neither predict nor handle itself.
甚至命名该方法也是一个很好的例子,说明您必须为一个情形字符串修改类的不变量和接口,它既不能预测也不能处理自己。
Validating input example
Let's address a common example: validating user input. Just because we want to allow for failed input doesn't mean the parsing function needs to include that in its postcondition. It does mean our handler needs to check if the parser fails, however.
我们来看一个常见的例子:验证用户输入。仅仅因为我们希望允许失败的输入,并不意味着解析函数需要在它的postcondition中包含它。但是,这确实意味着我们的处理程序需要检查解析器是否失败。
// 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';
}
Unfortunately, this shows two examples of how C++'s scoping requires you to break RAII/SBRM. An example in Python which doesn't have that problem and shows something I wish C++ had – try-else:
不幸的是,这展示了两个示例,说明c++的作用域要求您破坏RAII/SBRM。Python中的一个例子,它没有这个问题,并显示了我希望c++拥有的一些东西:try-else:
# 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
Preconditions
Preconditions don't strictly have to be checked – violating one always indicates a logic failure, and they are the caller's responsibility – but if you do check them, then throwing an exception is appropriate. (In some cases it's more appropriate to return garbage or crash the program; though those actions can be horribly wrong in other contexts. How to best handle undefined behavior is another topic.)
先决条件并不一定要被检查——违反规则总是意味着逻辑失败,而且它们是调用者的责任——但是如果您检查了它们,那么抛出异常是适当的。(在某些情况下,返回垃圾或崩溃程序更合适;尽管在其他情况下,这些行为可能是非常错误的。如何最好地处理未定义的行为是另一个话题。
In particular, contrast the std::logic_error and std::runtime_error branches of the stdlib exception hierarchy. The former is often used for precondition violations, while the latter is more suited for postcondition violations.
特别要对比std::logic_error和stdlib异常层次结构的runtime_error分支。前者通常用于前置违例,而后者更适合后置违例。
#3
39
-
Expensive
kernel calls (or other system API invocations) to manage kernel (system) signal interfaces - 昂贵的内核调用(或其他系统API调用)来管理内核(系统)信号接口
-
Hard to analyze
Many of the problems of thegoto
statement apply to exceptions. They jump over potentially large amounts of code often in multiple routines and source files. This is not always apparent from reading the intermediate source code. (It is in Java.) - 很难分析goto语句的许多问题适用于异常。它们经常在多个例程和源文件中跳过大量的代码。这在阅读中间源代码时并不总是显而易见的。(在Java)。
-
Not always anticipated by intermediate code
The code that gets jumped over may or may not have been written with the possibility of an exception exit in mind. If originally so written, it may not have been maintained with that in mind. Think: memory leaks, file descriptor leaks, socket leaks, who knows? - 中间代码并不总是预期要跳过的代码可能已经写过了,也可能没有写过,因为可能会有异常退出。如果最初是这样写的,那么它可能并没有被记住。想想:内存泄漏、文件描述符泄漏、套接字泄漏,谁知道呢?
-
Maintenance complications
It's harder to maintain code that jumps around processing exceptions. - 维护的复杂性很难维护在处理异常时跳转的代码。
#4
22
Throwing an exception is, to some extent, similar to a goto statement. Do that for flow control, and you end with incomprehensible spaghetti code. Even worse, in some cases you do not even know where exactly the jump goes to (i.e. if you are not catching the exception in the given context). This blatantly violates the "least surprise" principle that enhances maintainability.
在某种程度上,抛出异常与goto语句相似。对流控制这样做,您将以无法理解的通心粉代码结束。更糟糕的是,在某些情况下,您甚至不知道跳转的确切位置(即,如果您没有在给定的上下文中捕获异常)。这公然违反了增强可维护性的“最少惊喜”原则。
#5
16
Exceptions make it harder to reason about the state of your program. In C++ for instance, you have to do extra thinking to ensure your functions are strongly exception safe, than you would have to do if they didn't need to be.
异常使您很难推断程序的状态。例如,在c++中,您必须进行额外的思考,以确保您的函数是完全异常安全的,而不是在不需要的情况下。
The reason is that without exceptions, a function call can either return, or it can terminate the program first. With exceptions, a function call can either return, or it can terminate the program, or it can jump to a catch block somewhere. So you can no longer follow the flow of control just by looking at the code in front of you. You need to know if the functions called can throw. You may need to know what can be thrown and where it's caught, depending on whether you care where control goes, or only care that it leaves the current scope.
原因是没有异常,函数调用可以返回,也可以先终止程序。除了例外,函数调用可以返回,也可以终止程序,或者跳转到某个捕获块。因此,您不能仅仅通过查看前面的代码来跟踪控制流。您需要知道调用的函数是否可以抛出。您可能需要知道什么可以被抛出,以及它被捕获的位置,这取决于您是否关心控件的位置,或者只关心它离开当前的范围。
For this reason, people say "don't use exceptions unless the situation is really exceptional". When you get down to it, "really exceptional" means "some situation has occurred where the benefits of handling it with an error return value are outweighed by the costs". So yes, this is something of an empty statement, although once you have some instincts for "really exceptional", it becomes a good rule of thumb. When people talk about flow control, they mean that the ability to reason locally (without reference to catch blocks) is a benefit of return values.
出于这个原因,人们说“除非情况非常特殊,否则不要使用异常”。当你深入研究它时,“真正异常”意味着“在某些情况下,使用错误返回值处理它的好处被成本所抵消”。所以,是的,这是一种空洞的陈述,尽管一旦你有了“真正与众不同”的直觉,它就会成为一个很好的经验法则。当人们谈论流控制时,他们意味着本地推理(不涉及到捕获块)的能力是返回值的好处。
Java has a wider definition of "really exceptional" than C++. C++ programmers are more likely to want to look at the return value of a function than Java programmers, so in Java "really exceptional" might mean "I can't return a non-null object as the result of this function". In C++, it's more likely to mean "I very much doubt my caller can continue". So a Java stream throws if it can't read a file, whereas a C++ stream (by default) returns a value indicating error. In all cases, though, it is a matter of what code you are willing to force your caller to have to write. So it is indeed a matter of coding style: you have to reach a consensus what your code should look like, and how much "error-checking" code you want to write against how much "exception-safety" reasoning you want to do.
Java对“真正异常”的定义比c++更广泛。与Java程序员相比,c++程序员更希望查看函数的返回值,因此在Java中,“非常异常”可能意味着“由于这个函数,我不能返回非空对象”。在c++中,它更可能意味着“我非常怀疑我的调用者能否继续”。因此,如果Java流不能读取文件,它就会抛出,而c++流(默认情况下)返回一个值,指示错误。不过,在所有情况下,问题都在于您愿意迫使调用者编写什么代码。因此,这确实是一个编码风格的问题:您必须达成一致,您的代码应该是什么样子,以及您想要编写多少“错误检查”代码,而您又想要编写多少“异常安全”推理。
The broad consensus across all languages seems to be that this is best done in terms of how recoverable the error is likely to be (since unrecoverable errors result in no code with exceptions, but still need a check-and-return-your-own-error in code which uses error returns). So people come to expect "this function I call throws an exception" to mean "I can't continue", not just "it can't continue". That's not inherent in exceptions, it's just a custom, but like any good programming practice, it's a custom advocated by smart people who've tried it the other way and not enjoyed the results. I too have had bad experiences throwing too many exceptions. So personally, I do think in terms of "really exceptional", unless something about the situation makes an exception particularly attractive.
所有语言的广泛共识似乎都是这样做的:根据错误的可恢复程度,这是最好的(因为不可恢复的错误导致没有异常的代码,但是仍然需要在使用错误返回的代码中检查并返回您自己的错误)。所以人们开始期待“我称之为抛出异常的这个函数”意味着“我不能继续”,而不仅仅是“它不能继续”。这并不是固有的例外,它只是一种习惯,但就像任何好的编程实践一样,这是聪明的人提倡的一种习惯,他们尝试过另一种方式,但并不喜欢结果。我也有过失败的经历,有太多的例外。因此,我个人认为,在“非常特殊”的情况下,除非某些情况特别吸引人。
Btw, quite aside from reasoning about the state of your code, there are also performance implications. Exceptions are usually cheap now, in languages where you're entitled to care about performance. They can be faster than multiple levels of "oh, the result's an error, I'd best exit myself with an error too, then". In the bad old days, there were real fears that throwing an exception, catching it, and carrying on with the next thing, would make what you're doing so slow as to be useless. So in that case, "really exceptional" means, "the situation is so bad that horrific performance no longer matters". That's no longer the case (although an exception in a tight loop is still noticeable) and hopefully indicates why the definition of "really exceptional" needs to be flexible.
顺便说一句,除了对代码的状态进行推理之外,还有性能方面的影响。异常通常是廉价的,在语言中你有权关心性能。它们可能比“哦,结果是一个错误,我最好也用一个错误退出我自己”的多个级别要快。在过去的糟糕日子里,人们确实担心,抛出一个异常,抓住它,然后继续做下一件事,会使你正在做的事情变得如此缓慢,以至于毫无用处。因此,在这种情况下,“真正的例外”意味着“形势如此糟糕,可怕的表现不再重要”。这不再是问题(尽管在紧密循环中有一个例外仍然值得注意),并希望说明为什么“真正异常”的定义需要灵活。
#6
11
There really is no consensus. The whole issue is somewhat subjective, because the "appropriateness" of throwing an exception is often suggested by existing practices within the standard library of the language itself. The C++ standard library throws exceptions a lot less frequently than say, the Java standard library, which almost always prefers exceptions, even for expected errors such as invalid user input (e.g. Scanner.nextInt
). This, I believe, significantly influences developer opinions about when it is appropriate to throw an exception.
真的没有共识。整个问题有点主观,因为在语言本身的标准库中,经常会建议使用“适当性”来抛出异常。与Java标准库相比,c++标准库抛出异常的频率要低得多,Java标准库几乎总是更喜欢异常,即使对于预期的错误,比如无效的用户输入(如Scanner.nextInt)。我认为,这极大地影响了开发人员对何时应该抛出异常的看法。
As a C++ programmer, I personally prefer to reserve exceptions for very "exceptional" circumstances, e.g. out of memory, out of disk-space, the apocalypse happened, etc. But I don't insist that this is the absolute correct way to do things.
作为一个c++程序员,我个人更倾向于为非常“特殊”的情况保留异常,例如内存不足、磁盘空间不足、灾难发生等等。但我不认为这是绝对正确的做事方式。
#7
7
I don't think, that exceptions should rarely be used. But.
我不认为,这种例外应该很少被使用。但是。
Not all teams and projects are ready to use exceptions. Usage of exceptions requires high qualification of programmers, special technics and lack of big legacy non exception-safe code. If you have huge old codebase, then it almost always is not exception-safe. I'm sure that you do not want to rewrite it.
不是所有的团队和项目都准备好使用异常。异常的使用需要程序员的高资格、特殊的技术以及缺乏大型遗留的非异常安全代码。如果您有很大的旧代码基,那么它几乎总是不例外的安全。我肯定你不想重写它。
If you are going to use exceptions extensively, then:
如果你要广泛使用异常,那么:
- be prepared to teach your people about what exception safety is
- 准备好告诉你的员工什么是异常安全
- you should not use raw memory management
- 不应该使用原始内存管理
- use RAII extensively
- 大量使用RAII
From the other hand, using exceptions in new projects with strong team may make code cleaner, easier to maintain, and even faster:
另一方面,在具有强大团队的新项目中使用异常可以使代码更清晰、更容易维护,甚至更快:
- you will not miss or ignore errors
- 您不会忽略或忽略错误
- you haven't to write that checks of return codes, without actually knowing what to do with wrong code at low-level
- 您不必编写返回代码的检查,而实际上不知道如何在底层处理错误的代码
- when you are forced to write exception-safe code, it becomes more structured
- 当您*编写异常安全的代码时,它会变得更加结构化。
#8
7
EDIT 11/20/2009:
编辑11/20/2009:
I was just reading this MSDN article on improving managed code performance and this part reminded me of this question:
我刚刚阅读了MSDN关于提高托管代码性能的文章,这部分让我想起了这个问题:
The performance cost of throwing an exception is significant. Although structured exception handling is the recommended way of handling error conditions, make sure you use exceptions only in exceptional circumstances when error conditions occur. Do not use exceptions for regular control flow.
抛出异常的性能代价是巨大的。尽管结构化异常处理是处理错误条件的推荐方式,但请确保只有在出现错误条件的异常情况下才使用异常。不要对常规控制流使用异常。
Of course, this is only for .NET, and it's also directed specifically at those developing high-performance applications (like myself); so it's obviously not a universal truth. Still, there are a lot of us .NET developers out there, so I felt it was worth noting.
当然,这只适用于。net,它还专门针对那些开发高性能应用程序的人(比如我自己);显然这不是一个普遍的真理。尽管如此,我们还是有很多的。net开发人员,所以我觉得值得注意。
EDIT:
编辑:
OK, first of all, let's get one thing straight: I have no intention of picking a fight with anyone over the performance question. In general, in fact, I am inclined to agree with those who believe premature optimization is a sin. However, let me just make two points:
好吧,首先,让我们弄清楚一件事:我无意就表演问题与任何人争论。总的来说,事实上,我倾向于同意那些认为过早优化是一种罪恶的观点。但是,我想说两点:
-
The poster is asking for an objective rationale behind the conventional wisdom that exceptions should be used sparingly. We can discuss readability and proper design all we want; but these are subjective matters with people ready to argue on either side. I think the poster is aware of this. The fact is that using exceptions to control program flow is often an inefficient way of doing things. No, not always, but often. This is why it's reasonable advice to use exceptions sparingly, just like it's good advice to eat red meat or drink wine sparingly.
这张海报在寻求一种客观的理由来支持常规的观点,即应该谨慎地使用例外。我们可以讨论可读性和适当的设计,我们想;但这些都是主观的问题,人们愿意在任何一方争论。我想海报已经意识到了这一点。事实上,使用异常来控制程序流通常是一种低效的方法。不,不总是,但经常。这就是为什么谨慎地使用异常是合理的建议,就像谨慎地吃红肉或喝酒一样。
-
There is a difference between optimizing for no good reason and writing efficient code. The corollary to this is that there's a difference between writing something that is robust, if not optimized, and something that is just plain inefficient. Sometimes I think when people argue over things like exception handling they're really just talking past each other, because they are discussing fundamentally different things.
无理由的优化和编写高效的代码之间是有区别的。由此得出的结论是,编写健壮(如果没有优化的话)的代码和编写效率很低的代码是有区别的。有时我认为当人们争论诸如异常处理之类的事情时他们实际上只是在讨论彼此,因为他们讨论的是根本不同的事情。
To illustrate my point, consider the following C# code examples.
为了说明我的观点,请考虑以下c#代码示例。
Example 1: Detecting invalid user input
This is an example of what I'd call exception abuse.
这是我称之为例外滥用的一个例子。
int value = -1;
string input = GetInput();
bool inputChecksOut = false;
while (!inputChecksOut) {
try {
value = int.Parse(input);
inputChecksOut = true;
} catch (FormatException) {
input = GetInput();
}
}
This code is, to me, ridiculous. Of course it works. No one's arguing with that. But it should be something like:
这段代码对我来说太荒谬了。当然它的工作原理。没人会反对。但它应该是这样的:
int value = -1;
string input = GetInput();
while (!int.TryParse(input, out value)) {
input = GetInput();
}
Example 2: Checking for the existence of a file
I think this scenario is actually very common. It certainly seems a lot more "acceptable" to a lot of people, since it deals with file I/O:
我认为这种情况很常见。对于很多人来说,它似乎更“可接受”,因为它处理文件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();
}
}
This seems reasonable enough, right? We're trying to open a file; if it's not there, we catch that exception and try to open a different file... What's wrong with that?
这似乎很合理,对吧?我们试图打开一个文件;如果它不在那里,我们捕获那个异常并尝试打开另一个文件……有什么问题吗?
Nothing, really. But consider this alternative, which doesn't throw any exceptions:
没什么,真的。但是考虑一下这个选项,它不会抛出任何异常:
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();
}
}
Of course, if the performance of these two approaches were actually the same, this really would be purely a doctrinal issue. So, let's take a look. For the first code example, I made a list of 10000 random strings, none of which represented a proper integer, and then added a valid integer string onto the very end. Using both of the above approaches, these were my results:
当然,如果这两种方法的表现实际上是一样的,这就纯粹是一个教条问题。我们来看看。对于第一个代码示例,我创建了一个包含10000个随机字符串的列表,其中没有一个字符串表示一个合适的整数,然后在末尾添加一个有效的整数字符串。使用上述两种方法,我的结果如下:
Using try
/catch
block: 25.455 seconds
Using int.TryParse
: 1.637 milliseconds
使用try/catch块:25.455秒使用int.TryParse: 1.637毫秒
For the second example, I did basically the same thing: made a list of 10000 random strings, none of which was a valid path, then added a valid path onto the very end. These were the results:
对于第二个示例,我基本上做了相同的事情:创建一个包含10000个随机字符串的列表,其中没有一个是有效的路径,然后在末尾添加一个有效的路径。这些结果:
Using try
/catch
block: 29.989 seconds
Using File.Exists
: 22.820 milliseconds
使用try/catch块:29.989秒使用文件。存在:22.820毫秒
A lot of people would respond to this by saying, "Yeah, well, throwing and catching 10,000 exceptions is extremely unrealistic; this exaggerates the results." Of course it does. The difference between throwing one exception and handling bad input on your own is not going to be noticeable to the user. The fact remains that using exceptions is, in these two case, from 1,000 to over 10,000 times slower than the alternative approaches that are just as readable -- if not more so.
很多人会这样回答:“嗯,投掷和捕捉10000个异常是非常不现实的;这夸大了结果。”当然它。抛出一个异常和自己处理错误输入之间的区别不会被用户注意到。事实仍然是,在这两种情况下,使用异常要比可读的其他方法慢1,000到10,000多倍——如果不是更慢的话。
That's why I included the example of the GetNine()
method below. It isn't that it's intolerably slow or unacceptably slow; it's that it's slower than it should be... for no good reason.
这就是为什么我在下面包含GetNine()方法的示例。并不是说它慢得让人无法忍受或者慢得让人无法接受;而是它比它应该的要慢……没有充分的理由。
Again, these are just two examples. Of course there will be times when the performance hit of using exceptions is not this severe (Pavel's right; after all, it does depend on the implementation). All I'm saying is: let's face the facts, guys -- in cases like the one above, throwing and catching an exception is analogous to GetNine()
; it's just an inefficient way of doing something that could easily be done better.
这只是两个例子。当然,有时使用异常的性能影响不是那么严重(Pavel是对的;毕竟,这取决于实现)。我想说的是:让我们面对事实——在上面的例子中,抛出和捕获一个异常类似于GetNine();这只是一种效率低下的方法,可以很容易地做得更好。
You are asking for a rationale as if this is one of those situations where everyone's jumped on a bandwagon without knowing why. But in fact the answer is obvious, and I think you know it already. Exception handling has horrendous performance.
你在问一个理由,就好像这是那种每个人都不知道为什么而随波逐流的情况。但事实上答案是显而易见的,我想你已经知道了。异常处理具有可怕的性能。
OK, maybe it's fine for your particularly business scenario, but relatively speaking, throwing/catching an exception introduces way more overhead than is necessary in many, many cases. You know it, I know it: most of the time, if you're using exceptions to control program flow, you're just writing slow code.
好的,对于您的特定业务场景来说,这也许是可以的,但是相对而言,抛出/捕获一个异常所带来的开销要比许多情况下需要的开销大得多。您知道,我知道:大多数情况下,如果您使用异常来控制程序流,那么您就是在编写慢代码。
You might as well ask: why is this code bad?
您可能还会问:为什么这段代码不好?
private int GetNine() {
for (int i = 0; i < 10; i++) {
if (i == 9) return i;
}
}
I would bet that if you profiled this function you'd find it performs quite acceptably fast for your typical business application. That doesn't change the fact that it's a horribly inefficient way of accomplishing something that could be done a lot better.
我敢打赌,如果您分析了这个函数,您会发现它对于典型的业务应用程序的执行速度非常快。这并不能改变一个事实,那就是这是一种非常低效的方式来完成一些可以做得更好的事情。
That's what people mean when they talk about exception "abuse."
这就是人们所说的例外“虐待”的意思。
#9
6
All of the rules of thumb about exceptions come down to subjective terms. You shouldn't expect to get hard and fast definitions of when to use them and when not to. "Only in exceptional circumstances". Nice circular definition: exceptions are for exceptional circumstances.
所有关于例外的经验法则都可以归结为主观因素。你不应该期望对何时使用它们以及何时使用它们有严格的定义。“只有在特殊的情况下”。良好的循环定义:异常是针对特殊情况的。
When to use exceptions falls into the same bucket as "how do I know whether this code is one class or two?" It's partly a stylistic issue, partly a preference. Exceptions are a tool. They can be used and abused, and finding the line between the two is part of the art and skill of programming.
什么时候使用异常与“我如何知道这段代码是一个类还是两个类?”这部分是风格问题,部分是偏好问题。异常是一种工具。它们可以被使用和滥用,找到两者之间的界线是编程艺术和技巧的一部分。
There are lots of opinions, and tradeoffs to be made. Find something that speaks to you, and follow it.
有很多观点,需要权衡。找到一些对你说话的东西,然后跟随它。
#10
6
It's not that exceptions should rarely be used. It's just that they should only be thrown in exceptional circumstances. For example, if a user enters the wrong password, that's not exceptional.
并不是说应该很少使用异常。只是他们应该被扔在特殊的环境中。例如,如果用户输入了错误的密码,这不是例外。
The reason is simple: exceptions exit a function abruptly, and propagate up the stack to a catch
block. This process is very computationally expensive: C++ builds its exception system to have little overhead on "normal" function calls, so when an exception is raised, it has to do a lot of work to find where to go. Moreover, since every line of code could possibly raise an exception. If we have some function f
that raises exceptions often, we now have to take care to use our try
/catch
blocks around every call of f
. That's a pretty bad interface/implementation coupling.
原因很简单:异常会突然退出一个函数,并将堆栈向上传播到一个catch块。这个过程在计算上非常昂贵:c++构建了它的异常系统,在“正常”函数调用上几乎没有开销,因此当出现异常时,它必须做大量工作才能找到要去的地方。此外,因为每一行代码都可能引发异常。如果我们有一个经常引起异常的函数f,我们现在必须注意在每次调用f时使用try/catch块,这是一个非常糟糕的接口/实现耦合。
#11
5
I mentioned this issue in an article on C++ exceptions.
我在一篇关于c++异常的文章中提到了这个问题。
The relevant part:
相关的部分:
Almost always, using exceptions to affect the "normal" flow is a bad idea. As we already discussed in section 3.1, exceptions generate invisible code paths. These code paths are arguably acceptable if they get executed only in the error handling scenarios. However, if we use exceptions for any other purpose, our "normal" code execution is divided into a visible and invisible part and it makes code very hard to read, understand and extend.
几乎总是,使用异常来影响“正常”流不是一个好主意。正如我们在第3.1节中已经讨论过的,异常生成不可见的代码路径。如果只在错误处理场景中执行这些代码路径,那么这些代码路径是可以接受的。然而,如果我们将异常用于任何其他目的,我们的“正常”代码执行被划分为可见的和不可见的部分,这使得代码很难阅读、理解和扩展。
#12
5
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.
- 在错误站点可以处理的奇怪情况。这可能是如果用户在命令行提示符中输入无效的输入。在这种情况下,正确的行为是向用户投诉并循环。另一种情况是被0除。这些情况不是真正的错误情况,通常是由错误的输入引起的。
- 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.
- 类似于前一种情况,但是不能在错误站点中处理。例如,如果有一个函数接收文件名并解析文件名,它可能无法打开该文件。在这种情况下,它不能处理错误。这是例外出现的时候。与使用C方法(返回无效值作为标志并设置全局错误变量来指示问题)不同,代码可以抛出一个异常。然后,调用代码将能够处理异常——例如,为另一个文件名提示用户。
- 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. - 不应该发生的情况。这是当类不变量被违反时,或者函数接收无效的参数或类似的情况。这表示代码中的逻辑错误。根据故障级别的不同,可能需要一个异常,或者最好是强制立即终止(就像assert所做的那样)。一般来说,这些情况表明代码中的某个地方出了问题,您不能信任其他任何东西是正确的——可能存在严重的内存损坏。你的船要沉了,快下来。
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.
换句话说,例外是指当你有问题可以解决,但你不能在你注意到的地方解决。你无法处理的问题应该简单地终止程序;你可以马上解决的问题应该简单地解决。
#13
5
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:
我在这里读到了一些答案。我仍然对这些困惑感到惊讶。我强烈反对所有这些异常=spagetty代码。我的意思是,有些人不喜欢c++异常处理。我不确定我是如何了解c++异常处理的,但我在几分钟内就理解了它的含义。这是在1996年左右,我在使用borland c++编译器的OS/2。我从来没有一个问题来决定,什么时候使用例外。我通常将易出错的do-undo操作打包到c++类中。这种do-undo行为包括:
- creating/destroying a system handle (for files, memory maps, WIN32 GUI handles, sockets, and so on)
- 创建/销毁一个系统句柄(用于文件、内存映射、WIN32 GUI句柄、套接字等)
- 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++. E.g. read/write from/to a file. If something fails, an exception will be thrown, which contains full information about the error.
而不是功能性包装。将系统调用(不属于前一类)封装到c++中。从一个文件中读/写。如果发生故障,将抛出异常,其中包含有关错误的完整信息。
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.
总的来说,c++异常处理会带来更清晰的代码。代码的数量急剧减少。最后,可以使用构造函数来分配易出错的资源,并在发生此类故障后仍然保持无损坏的环境。
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.
可以将此类类链接到复杂的类中。一旦执行了某个成员/基对象的构造函数,就可以依赖于同一对象的所有其他构造函数(在成功执行之前执行)。
#14
3
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.
与传统的结构(循环、ifs、函数等)相比,异常是一种非常不常见的流控制方法。正常的控制流结构(循环、ifs、函数调用等)可以处理所有正常的情况。如果您发现自己遇到了一个例程异常,那么您可能需要考虑代码的结构。
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 (e.g., 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语句是不够的。这些类型的失败通常需要在更高的级别上处理(例如,保存文件、记录错误、退出)。尝试通过传统的方法(如返回值)报告这样的错误是冗长且容易出错的。此外,它将开销注入中层api层,以处理这种奇怪的、不寻常的失败。开销分散了这些api的客户端,并要求他们担心超出他们控制范围的问题。异常提供了一种方法来对大错误进行非本地处理,这些错误在问题的检测和处理程序之间的所有层中通常是不可见的。
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.
如果客户端使用字符串调用ParseInt,而字符串不包含整数,那么直接调用者可能会关心错误并知道如何处理它。你可以设计ParseInt来返回类似的失败代码。
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).
另一方面,如果ParseInt失败了,因为它不能分配一个缓冲区,因为内存是非常分散的,那么调用者就不知道该怎么做。它将不得不把这个不寻常的错误推到某一层来处理这些根本的失败。这使得每个人(因为他们必须在自己的api中容纳错误传递机制)之间的税收。一个异常使跳过这些层成为可能(同时仍然确保进行必要的清理)。
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.
在编写低级代码时,很难决定什么时候使用传统方法,什么时候抛出异常。低级代码必须作出决定(抛出或不抛出)。但是高级代码才是真正知道什么是期望的,什么是例外的。
#15
3
There's several reasons in C++.
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.
首先,通常很难看到异常来自哪里(因为它们几乎可以从任何东西中抛出),因此catch块是来自语句的某种东西。它比GO TO更糟糕,因为在GO TO中你知道你从哪里来(语句,而不是某个随机的函数调用)和你要去哪里(标签)。它们基本上是C的setjmp()和longjmp()的一个潜在资源安全版本,没有人愿意使用它们。
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.
其次,c++没有内置的垃圾收集,所以拥有资源的c++类会在它们的析构函数中删除它们。因此,在c++异常处理中,系统必须运行范围内的所有析构函数。在使用GC而没有真正的构造函数(如Java)的语言中,抛出异常要容易得多。
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.
第三,c++社区(包括Bjarne Stroustrup和标准委员会以及各种编译器作者)一直假定异常应该是异常的。总的来说,它不值得违背语言文化。这些实现基于一个假设,即异常将是罕见的。更好的书把例外视为例外。好的源代码很少使用异常。优秀的c++开发人员将异常视为异常。与之相反的是,你需要一个好的理由,而我看到的所有理由都是为了让他们与众不同。
#16
2
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"
异常也有一些与性能相关的惩罚,但是性能问题应该遵循以下规则:“过早优化是万恶之源”
#17
2
- Maintainability: As mentioned by people above, throwing exceptions at a drop of a hat is akin to using gotos.
- 可维护性:正如上面提到的,在一滴水中抛出异常类似于使用goto。
- Interoperability: You can't interface C++ libraries with C/Python modules (atleast not easily) if you are using exceptions.
- 互操作性:如果您使用异常,您不能使用C/Python模块(至少不容易)与c++库交互。
- 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).
- 性能下降:RTTI用于实际查找异常的类型,这会增加额外的开销。因此,异常不适用于处理常见的用例(用户输入int而不是string等)。
#18
2
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.
我想说,异常是一种以安全的方式将您从当前上下文(从最简单的意义上说,从当前堆栈框架中取出,但它不止于此)中取出的机制。这是最接近结构化编程的东西。要以预期的方式使用异常,您必须有这样一种情况:您无法继续您现在正在做的工作,并且您无法在当前的位置处理它。例如,当用户密码错误时,可以继续返回false。但是如果UI子系统报告它甚至不能提示用户,那么简单地返回“登录失败”将是错误的。当前级别的代码根本不知道该做什么。因此,它使用异常机制将责任委托给可能知道该做什么的人。
#19
2
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".
一个非常实际的原因是,在调试程序时,我经常会抛出第一次机会异常(Debug ->异常)来调试应用程序。如果有很多例外发生,就很难找到哪里出了问题。
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.
此外,它还会导致一些反模式,如臭名昭著的“接球”和混淆真正的问题。有关这方面的更多信息,请参阅我关于这一主题的博客文章。
#20
2
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.
谷歌的编码策略规定永远不要使用异常,特别是在c++中。您的应用程序要么不准备处理异常,要么准备处理异常。如果它不是,那么异常可能会传播到您的应用程序死亡为止。
It's never fun to find out some library you have used throws exceptions and you were not prepared to handle them.
查找您使用过的抛出异常的库,并且您不准备处理它们,这从来都不是一件有趣的事情。
#21
1
Legitimate case to throw an exception:
有正当理由提出例外:
- You try to open a file, it's not there, a FileNotFoundException is thrown;
- 你尝试打开一个文件,它不在那里,一个FileNotFoundException被抛出;
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.
- 只有当文件不存在时,您才需要做一些事情,您尝试打开文件,然后向catch块添加一些代码。
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.
当我想要将应用程序的流分解到某个点时,我使用异常。这一点是异常的捕获(…)所在。例如,我们经常需要处理大量的项目,每个项目都应该独立于其他项目进行处理。因此,处理项目的循环需要尝试……catch块,如果在项目处理过程中抛出异常,则该项目的所有内容都回滚,并记录错误,然后处理下一个项目。生活还在继续。
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.
也许我的示例不是很好——在第二个示例中,不应该抛出一个异常,如果有,那么就会有一些异常,应该在其他地方采取其他操作。当示例1被大量使用时,开发人员将捕获一个通用异常而不是特定的异常,并假定该异常是由于他们预期的错误造成的,但它可能是由于其他原因造成的。最后,这会导致真正的异常被忽略,因为异常成为应用程序流的一部分,而不是异常。
The comments on this may add more than my answer itself.
对此的评论可能不仅仅是我的回答。
#22
0
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.
当您希望在调用者忽略编写错误处理代码的情况下提供一个正常的默认值时,或者如果错误最好是在调用堆栈中比直接调用者处理得更高级时,应该使用IMHO异常。正常的默认情况是退出带有合理诊断错误消息的程序。疯狂的替代方案是程序在错误的状态中蹒跚前进,崩溃或者在以后的某个时候悄无声息地产生糟糕的输出,这是很难诊断的。如果“错误”是程序流的正常部分,调用者无法合理地忘记检查它,那么就不应该使用异常。
#23
0
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.
原因是,异常不仅是非常强大的goto语句,它们还必须为它们所留下的所有帧展开堆栈。因此,栈上的对象也必然要被解构,等等。所以如果没有意识到这一点,一个异常的抛出就会涉及到一系列的力学问题。处理器需要做很多事情。
So you will end up, elegantly burning your processor without knowing.
所以你最终会在不知不觉中优雅地烧掉你的处理器。
So: use exceptions only in exceptional cases -- Meaning: When real errors occured!
所以:只在异常情况下使用异常——意思是:当出现真正的错误时!
#24
0
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.
我认为好的编程语言不支持异常,正如我们在c++和Java中所知道的那样。您应该选择可以为函数的各种返回值提供可选流的编程语言。程序员应该负责预测一个例程的所有形式的输出,并在一个独立的代码文件中处理它们。
#25
0
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.).
如果可以从(用户输入“apple”而不是数字)恢复错误,则恢复(再次请求输入,更改为默认值,等等)。
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.
如果不能从本地恢复错误,并且应用程序不能在不恢复的情况下继续运行(内存/磁盘空间/等等),那么异常是正确的方法。
#26
0
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 ?
谁说他们应该保守地使用?只是不要在流控制中使用异常。当异常已经抛出时,谁会关心异常的代价呢?
#27
0
My two cents:
我的两个美分:
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.
我喜欢使用异常,因为它允许我编程,就好像不会发生错误一样。因此,我的代码仍然是可读的,而不是散布在各种错误处理中。当然,错误处理(异常处理)被移动到末尾(catch块),或者被认为是调用级别的响应性。
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.
异常的问题是,它会很快变得非常冗长。虽然它的目的是让您的代码保持可读性,并且只关注正常的事物流,但是如果始终如一地使用,几乎每个函数调用都需要封装在try/catch块中,并且它开始破坏目的。
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
对于前面提到的ParseInt,我喜欢异常的概念。只是返回值。如果参数不可解析,则抛出异常。它让你的代码更简洁。在调用级别,您需要做一些类似的事情
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. E.g.
是干净的代码。当我得到这样分散的ParseInt时,我创建了处理异常并返回默认值的包装器函数。如。
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.
- 仍然需要在某些地方处理异常。额外问题:c++没有允许它指定函数可能抛出哪些异常的语法(像java那样)。因此调用级别不知道需要处理哪些异常。
- sometimes code can get very verbose, if every function needs to be wrapped in a try/catch block. But sometimes this still makes sense.
- 如果每个函数都需要在try/catch块中进行包装,那么有时代码会变得非常冗长。但有时这还是有道理的。
So that makes it hard to find a good balance sometimes.
所以有时候很难找到一个好的平衡。
#28
-1
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....
几乎所有的编程规则是一个指导方针说“别这样做,除非你有一个很好的理由”:“千万不要使用goto”,“避免全局变量”、“正则表达式pre-increment你许多问题通过一个“,等。异常也不例外....
#1
88
The primary point of friction is semantics. Many developers abuse exceptions and throw them at every opportunity. The idea is to use exception for somewhat exceptional situation. For example, wrong user input does not count as an exception because you expect this to happen and ready for that. But if you tried to create a file and there was not enough space on disk, then yes, this is a definite exception.
摩擦的主要点是语义。许多开发人员滥用异常并抓住每一个机会抛出异常。我们的想法是在异常情况下使用异常。例如,错误的用户输入并不能算作一个异常,因为您希望发生这种情况并为此做好准备。但是,如果您尝试创建一个文件,而磁盘上没有足够的空间,那么是的,这是一个明确的例外。
One other issue is that exceptions are often thrown and swallowed. Developers use this technique to simply "silence" the program and let it run as long as possible until completely collapsing. This is very wrong. If you don't process exceptions, if you don't react appropriately by freeing some resources, if you don't log the exception occurrence or at least not notify the user, then you're not using exception for what they are meant.
另一个问题是,异常常常被抛出和吞噬。开发人员使用这种技术来简单地“屏蔽”程序,并让它尽可能长时间地运行,直到完全崩溃为止。这是非常错误的。如果您不处理异常,如果您没有通过释放一些资源来进行适当的响应,如果您没有记录异常发生,或者至少没有通知用户,那么您就没有为它们的含义使用异常。
Answering directly your question. Exceptions should rarely be used because exceptional situations are rare and exceptions are expensive.
直接回答你的问题。不应该使用异常,因为异常情况很少,并且异常代价高昂。
Rare, because you don't expect your program crash at every button press or at every malformed user input. Say, database may suddenly not be accessible, there may not be enough space on disk, some third party service you depend on is offline, this all can happen, but quite rarely, these would be clear exceptional cases.
很少,因为你不会期望你的程序在每一个按钮按下或每一个错误的用户输入时崩溃。比方说,数据库可能突然无法访问,磁盘上可能没有足够的空间,你依赖的第三方服务是离线的,这一切都可能发生,但很少,这些都是很明显的例外情况。
Expensive, because throwing an exception will interrupt the normal program flow. The runtime will unwind the stack until it finds an appropriate exception handler that can handle the exception. It will also gather the call information all along the way to be passed to the exception object the handler will receive. It all has costs.
代价高昂,因为抛出异常将中断正常的程序流。运行时将展开堆栈,直到找到可以处理异常的适当异常处理程序。它还将收集调用信息,一直传递到处理程序将接收的异常对象。它都有成本。
This is not to say that there can be no exception to using exceptions (smile). Sometimes it can simplify the code structure if you throw an exception instead of forwarding return codes via many layers. As a simple rule, if you expect some method to be called often and discover some "exceptional" situation half the time then it is better to find another solution. If however you expect normal flow of operation most of the time while this "exceptional" situation can only emerge in some rare circumstances, then it is just fine to throw an exception.
这并不是说使用异常(smile)没有例外。有时,如果抛出异常而不是通过多个层转发返回代码,则可以简化代码结构。作为一个简单的规则,如果您期望某些方法经常被调用,并且偶尔发现一些“异常”情况,那么最好找到另一个解决方案。但是,如果您期望大多数情况下正常的操作流程,而这种“异常”情况只能在一些罕见的情况下出现,那么抛出一个异常就可以了。
@Comments: Exception can definitely be used in some less-exceptional situations if that could make your code simpler and easier. This option is open but I'd say it comes quite rare in practice.
@Comments:在一些不太特殊的情况下可以使用异常,这样可以使代码更简单、更简单。这个选项是开放的,但我得说它在实践中非常罕见。
Why is it unwise to use them for control flow?
为什么将它们用于控制流是不明智的?
Because exceptions disrupt normal "control flow". You raise an exception and normal execution of the program is abandoned potentially leaving objects in inconsistent state and some open resources unfreed. Sure, C# has the using statement which will make sure the object will be disposed even if an exception is thrown from the using body. But let us abstract for the moment from the language. Suppose the framework won't dispose objects for you. You do it manually. You have some system for how to request and free resources and memory. You have agreement system-wide who is responsible for freeing objects and resources in what situations. You have rules how to deal with external libraries. It works great if the program follows the normal operation flow. But suddenly in the middle of execution you throw an exception. Half of the resources are left unfreed. Half have not been requested yet. If the operation was meant to be transactional now it is broken. Your rules for handling resources will not work because those code parts responsible for freeing resources simply won't execute. If anybody else wanted to use those resources they may find them in inconsistent state and crash as well because they could not predict this particular situation.
因为异常会破坏正常的“控制流”。您将引发一个异常,程序的正常执行将被放弃,可能会使对象处于不一致的状态,并使一些开放资源无法释放。当然,c#有使用语句,它将确保即使从使用主体抛出异常,也会对该对象进行处理。但是让我们暂时从语言中抽象出来。假设框架不会为您配置对象。你做手工。您有一些关于如何请求和释放资源和内存的系统。您在系统范围内拥有协议,负责在什么情况下释放对象和资源。您有如何处理外部库的规则。如果程序遵循正常的操作流程,那么它会工作得很好。但是突然在执行过程中你抛出了一个异常。一半的资源没有被释放。其中一半还没有被要求。如果操作本来是事务性的,现在它被破坏了。您处理资源的规则不会起作用,因为负责释放资源的代码部分不会执行。如果其他人想要使用这些资源他们可能会发现它们处于不一致的状态并且崩溃,因为他们无法预测这种特殊情况。
Say, you wanted a method M() call method N() to do some work and arrange for some resource then return it back to M() which will use it and then dispose it. Fine. Now something goes wrong in N() and it throws an exception you didn't expect in M() so the exception bubbles to the top until it maybe gets caught in some method C() which will have no idea what was happening deep down in N() and whether and how to free some resources.
例如,您希望一个方法M()调用方法N()来做一些工作并安排一些资源,然后将它返回给M(), M()将使用它,然后处理它。很好。现在出现问题在N()抛出一个异常你没想到在M()所以除了泡沫顶部,直到它也许会陷入一些C()方法将不知道发生了什么在N()和内心深处是否以及如何免费的一些资源。
With throwing exceptions you create a way to bring your program into many new unpredictable intermediate states which are hard to prognose, understand and deal with. It's somewhat similar to using GOTO. It is very hard to design a program that can randomly jump its execution from one location to the other. It will also be hard to maintain and debug it. When the program grows in complexity, you just going to lose an overview of what when and where is happening less to fix it.
除了抛出异常之外,您还创建了一种方法,使您的程序进入许多难以预测的中间状态,这些状态很难探测、理解和处理。它有点类似于使用后藤。要设计一个程序来随机地从一个位置跳到另一个位置是非常困难的。它也很难维护和调试。当程序变得复杂的时候,你就会忽略什么时候和什么地方发生的事情更少去修复它。
#2
60
While "throw exceptions in exceptional cirumstances" is the glib answer, you can actually define what those circumstances are: when preconditions are satisfied, but postconditions cannot be satisfied. This allows you to write stricter, tighter, and more useful postconditions without sacrificing error-handling; otherwise, without exceptions, you have to change the postcondition to allow for every possible error state.
虽然“异常情况下的抛出异常”是一个简短的回答,但您实际上可以定义这些情况是什么:当满足了先决条件,但不能满足后置条件。这允许您在不牺牲错误处理的情况下编写更严格、更严格和更有用的后置条件;否则,您必须更改postcondition,以允许每个可能的错误状态。
- Preconditions must be true before calling a function.
- 在调用函数之前,先决条件必须为真。
- Postcondition is what the function guarantees after it returns.
- 后置条件是函数在返回后所保证的。
- Exception safety states how exceptions affect the internal consistency of a function or data structure, and often deal with behavior passed in from outside (e.g. functor, ctor of a template parameter, etc.).
- 异常安全性说明异常如何影响函数或数据结构的内部一致性,并经常处理从外部传入的行为(例如:函子、模板参数的ctor等)。
Constructors
There's very little you can say about every constructor for every class that could possibly be written in C++, but there are a few things. Chief among them is that constructed objects (i.e. for which the constructor succeeded by returning) will be destructed. You cannot modify this postcondition because the language assumes it is true, and will call destructors automatically. (Technically you can accept the possibility of undefined behavior for which the language makes no guarantees about anything, but that is probably better covered elsewhere.)
对于每一个可以用c++编写的类,你几乎可以说不出每一个构造函数,但是有一些东西。其中最主要的是构造对象(即构造函数成功返回的对象)将被销毁。您不能修改这个后置条件,因为语言假定它为真,并将自动调用析构函数。(从技术上讲,您可以接受语言对任何事情都没有保证的不确定行为的可能性,但这在其他地方可能更好。)
The only alternative to throwing an exception when a constructor cannot succeed is to modify the basic definition of the class (the "class invariant") to allow valid "null" or zombie states and thus allow the constructor to "succeed" by constructing a zombie.
在构造函数不能成功时抛出异常的唯一替代方法是修改类的基本定义(“类不变量”),以允许有效的“空”或“僵尸”状态,从而允许构造函数通过构造一个僵尸来“成功”。
Zombie example
An example of this zombie modification is std::ifstream, and you must always check its state before you can use it. Because std::string, for example, doesn't, you are always guaranteed that you can use it immediately after construction. Imagine if you had to write code such as this example, and if you forgot to check for the zombie state, you'd either silently get incorrect results or corrupt other parts of your program:
这种僵尸修改的一个例子是std::ifstream,在使用它之前必须始终检查它的状态。因为std::string,例如,没有,您总是被保证在构建之后可以立即使用它。想象一下,如果您必须编写这样的代码,并且如果您忘记检查僵尸状态,那么您要么默默地得到错误的结果,要么破坏程序的其他部分:
string s = "abc";
if (s.memory_allocation_succeeded()) {
do_something_with(s); // etc.
}
Even naming that method is a good example of how you must modify the class' invariant and interface for a situation string can neither predict nor handle itself.
甚至命名该方法也是一个很好的例子,说明您必须为一个情形字符串修改类的不变量和接口,它既不能预测也不能处理自己。
Validating input example
Let's address a common example: validating user input. Just because we want to allow for failed input doesn't mean the parsing function needs to include that in its postcondition. It does mean our handler needs to check if the parser fails, however.
我们来看一个常见的例子:验证用户输入。仅仅因为我们希望允许失败的输入,并不意味着解析函数需要在它的postcondition中包含它。但是,这确实意味着我们的处理程序需要检查解析器是否失败。
// 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';
}
Unfortunately, this shows two examples of how C++'s scoping requires you to break RAII/SBRM. An example in Python which doesn't have that problem and shows something I wish C++ had – try-else:
不幸的是,这展示了两个示例,说明c++的作用域要求您破坏RAII/SBRM。Python中的一个例子,它没有这个问题,并显示了我希望c++拥有的一些东西:try-else:
# 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
Preconditions
Preconditions don't strictly have to be checked – violating one always indicates a logic failure, and they are the caller's responsibility – but if you do check them, then throwing an exception is appropriate. (In some cases it's more appropriate to return garbage or crash the program; though those actions can be horribly wrong in other contexts. How to best handle undefined behavior is another topic.)
先决条件并不一定要被检查——违反规则总是意味着逻辑失败,而且它们是调用者的责任——但是如果您检查了它们,那么抛出异常是适当的。(在某些情况下,返回垃圾或崩溃程序更合适;尽管在其他情况下,这些行为可能是非常错误的。如何最好地处理未定义的行为是另一个话题。
In particular, contrast the std::logic_error and std::runtime_error branches of the stdlib exception hierarchy. The former is often used for precondition violations, while the latter is more suited for postcondition violations.
特别要对比std::logic_error和stdlib异常层次结构的runtime_error分支。前者通常用于前置违例,而后者更适合后置违例。
#3
39
-
Expensive
kernel calls (or other system API invocations) to manage kernel (system) signal interfaces - 昂贵的内核调用(或其他系统API调用)来管理内核(系统)信号接口
-
Hard to analyze
Many of the problems of thegoto
statement apply to exceptions. They jump over potentially large amounts of code often in multiple routines and source files. This is not always apparent from reading the intermediate source code. (It is in Java.) - 很难分析goto语句的许多问题适用于异常。它们经常在多个例程和源文件中跳过大量的代码。这在阅读中间源代码时并不总是显而易见的。(在Java)。
-
Not always anticipated by intermediate code
The code that gets jumped over may or may not have been written with the possibility of an exception exit in mind. If originally so written, it may not have been maintained with that in mind. Think: memory leaks, file descriptor leaks, socket leaks, who knows? - 中间代码并不总是预期要跳过的代码可能已经写过了,也可能没有写过,因为可能会有异常退出。如果最初是这样写的,那么它可能并没有被记住。想想:内存泄漏、文件描述符泄漏、套接字泄漏,谁知道呢?
-
Maintenance complications
It's harder to maintain code that jumps around processing exceptions. - 维护的复杂性很难维护在处理异常时跳转的代码。
#4
22
Throwing an exception is, to some extent, similar to a goto statement. Do that for flow control, and you end with incomprehensible spaghetti code. Even worse, in some cases you do not even know where exactly the jump goes to (i.e. if you are not catching the exception in the given context). This blatantly violates the "least surprise" principle that enhances maintainability.
在某种程度上,抛出异常与goto语句相似。对流控制这样做,您将以无法理解的通心粉代码结束。更糟糕的是,在某些情况下,您甚至不知道跳转的确切位置(即,如果您没有在给定的上下文中捕获异常)。这公然违反了增强可维护性的“最少惊喜”原则。
#5
16
Exceptions make it harder to reason about the state of your program. In C++ for instance, you have to do extra thinking to ensure your functions are strongly exception safe, than you would have to do if they didn't need to be.
异常使您很难推断程序的状态。例如,在c++中,您必须进行额外的思考,以确保您的函数是完全异常安全的,而不是在不需要的情况下。
The reason is that without exceptions, a function call can either return, or it can terminate the program first. With exceptions, a function call can either return, or it can terminate the program, or it can jump to a catch block somewhere. So you can no longer follow the flow of control just by looking at the code in front of you. You need to know if the functions called can throw. You may need to know what can be thrown and where it's caught, depending on whether you care where control goes, or only care that it leaves the current scope.
原因是没有异常,函数调用可以返回,也可以先终止程序。除了例外,函数调用可以返回,也可以终止程序,或者跳转到某个捕获块。因此,您不能仅仅通过查看前面的代码来跟踪控制流。您需要知道调用的函数是否可以抛出。您可能需要知道什么可以被抛出,以及它被捕获的位置,这取决于您是否关心控件的位置,或者只关心它离开当前的范围。
For this reason, people say "don't use exceptions unless the situation is really exceptional". When you get down to it, "really exceptional" means "some situation has occurred where the benefits of handling it with an error return value are outweighed by the costs". So yes, this is something of an empty statement, although once you have some instincts for "really exceptional", it becomes a good rule of thumb. When people talk about flow control, they mean that the ability to reason locally (without reference to catch blocks) is a benefit of return values.
出于这个原因,人们说“除非情况非常特殊,否则不要使用异常”。当你深入研究它时,“真正异常”意味着“在某些情况下,使用错误返回值处理它的好处被成本所抵消”。所以,是的,这是一种空洞的陈述,尽管一旦你有了“真正与众不同”的直觉,它就会成为一个很好的经验法则。当人们谈论流控制时,他们意味着本地推理(不涉及到捕获块)的能力是返回值的好处。
Java has a wider definition of "really exceptional" than C++. C++ programmers are more likely to want to look at the return value of a function than Java programmers, so in Java "really exceptional" might mean "I can't return a non-null object as the result of this function". In C++, it's more likely to mean "I very much doubt my caller can continue". So a Java stream throws if it can't read a file, whereas a C++ stream (by default) returns a value indicating error. In all cases, though, it is a matter of what code you are willing to force your caller to have to write. So it is indeed a matter of coding style: you have to reach a consensus what your code should look like, and how much "error-checking" code you want to write against how much "exception-safety" reasoning you want to do.
Java对“真正异常”的定义比c++更广泛。与Java程序员相比,c++程序员更希望查看函数的返回值,因此在Java中,“非常异常”可能意味着“由于这个函数,我不能返回非空对象”。在c++中,它更可能意味着“我非常怀疑我的调用者能否继续”。因此,如果Java流不能读取文件,它就会抛出,而c++流(默认情况下)返回一个值,指示错误。不过,在所有情况下,问题都在于您愿意迫使调用者编写什么代码。因此,这确实是一个编码风格的问题:您必须达成一致,您的代码应该是什么样子,以及您想要编写多少“错误检查”代码,而您又想要编写多少“异常安全”推理。
The broad consensus across all languages seems to be that this is best done in terms of how recoverable the error is likely to be (since unrecoverable errors result in no code with exceptions, but still need a check-and-return-your-own-error in code which uses error returns). So people come to expect "this function I call throws an exception" to mean "I can't continue", not just "it can't continue". That's not inherent in exceptions, it's just a custom, but like any good programming practice, it's a custom advocated by smart people who've tried it the other way and not enjoyed the results. I too have had bad experiences throwing too many exceptions. So personally, I do think in terms of "really exceptional", unless something about the situation makes an exception particularly attractive.
所有语言的广泛共识似乎都是这样做的:根据错误的可恢复程度,这是最好的(因为不可恢复的错误导致没有异常的代码,但是仍然需要在使用错误返回的代码中检查并返回您自己的错误)。所以人们开始期待“我称之为抛出异常的这个函数”意味着“我不能继续”,而不仅仅是“它不能继续”。这并不是固有的例外,它只是一种习惯,但就像任何好的编程实践一样,这是聪明的人提倡的一种习惯,他们尝试过另一种方式,但并不喜欢结果。我也有过失败的经历,有太多的例外。因此,我个人认为,在“非常特殊”的情况下,除非某些情况特别吸引人。
Btw, quite aside from reasoning about the state of your code, there are also performance implications. Exceptions are usually cheap now, in languages where you're entitled to care about performance. They can be faster than multiple levels of "oh, the result's an error, I'd best exit myself with an error too, then". In the bad old days, there were real fears that throwing an exception, catching it, and carrying on with the next thing, would make what you're doing so slow as to be useless. So in that case, "really exceptional" means, "the situation is so bad that horrific performance no longer matters". That's no longer the case (although an exception in a tight loop is still noticeable) and hopefully indicates why the definition of "really exceptional" needs to be flexible.
顺便说一句,除了对代码的状态进行推理之外,还有性能方面的影响。异常通常是廉价的,在语言中你有权关心性能。它们可能比“哦,结果是一个错误,我最好也用一个错误退出我自己”的多个级别要快。在过去的糟糕日子里,人们确实担心,抛出一个异常,抓住它,然后继续做下一件事,会使你正在做的事情变得如此缓慢,以至于毫无用处。因此,在这种情况下,“真正的例外”意味着“形势如此糟糕,可怕的表现不再重要”。这不再是问题(尽管在紧密循环中有一个例外仍然值得注意),并希望说明为什么“真正异常”的定义需要灵活。
#6
11
There really is no consensus. The whole issue is somewhat subjective, because the "appropriateness" of throwing an exception is often suggested by existing practices within the standard library of the language itself. The C++ standard library throws exceptions a lot less frequently than say, the Java standard library, which almost always prefers exceptions, even for expected errors such as invalid user input (e.g. Scanner.nextInt
). This, I believe, significantly influences developer opinions about when it is appropriate to throw an exception.
真的没有共识。整个问题有点主观,因为在语言本身的标准库中,经常会建议使用“适当性”来抛出异常。与Java标准库相比,c++标准库抛出异常的频率要低得多,Java标准库几乎总是更喜欢异常,即使对于预期的错误,比如无效的用户输入(如Scanner.nextInt)。我认为,这极大地影响了开发人员对何时应该抛出异常的看法。
As a C++ programmer, I personally prefer to reserve exceptions for very "exceptional" circumstances, e.g. out of memory, out of disk-space, the apocalypse happened, etc. But I don't insist that this is the absolute correct way to do things.
作为一个c++程序员,我个人更倾向于为非常“特殊”的情况保留异常,例如内存不足、磁盘空间不足、灾难发生等等。但我不认为这是绝对正确的做事方式。
#7
7
I don't think, that exceptions should rarely be used. But.
我不认为,这种例外应该很少被使用。但是。
Not all teams and projects are ready to use exceptions. Usage of exceptions requires high qualification of programmers, special technics and lack of big legacy non exception-safe code. If you have huge old codebase, then it almost always is not exception-safe. I'm sure that you do not want to rewrite it.
不是所有的团队和项目都准备好使用异常。异常的使用需要程序员的高资格、特殊的技术以及缺乏大型遗留的非异常安全代码。如果您有很大的旧代码基,那么它几乎总是不例外的安全。我肯定你不想重写它。
If you are going to use exceptions extensively, then:
如果你要广泛使用异常,那么:
- be prepared to teach your people about what exception safety is
- 准备好告诉你的员工什么是异常安全
- you should not use raw memory management
- 不应该使用原始内存管理
- use RAII extensively
- 大量使用RAII
From the other hand, using exceptions in new projects with strong team may make code cleaner, easier to maintain, and even faster:
另一方面,在具有强大团队的新项目中使用异常可以使代码更清晰、更容易维护,甚至更快:
- you will not miss or ignore errors
- 您不会忽略或忽略错误
- you haven't to write that checks of return codes, without actually knowing what to do with wrong code at low-level
- 您不必编写返回代码的检查,而实际上不知道如何在底层处理错误的代码
- when you are forced to write exception-safe code, it becomes more structured
- 当您*编写异常安全的代码时,它会变得更加结构化。
#8
7
EDIT 11/20/2009:
编辑11/20/2009:
I was just reading this MSDN article on improving managed code performance and this part reminded me of this question:
我刚刚阅读了MSDN关于提高托管代码性能的文章,这部分让我想起了这个问题:
The performance cost of throwing an exception is significant. Although structured exception handling is the recommended way of handling error conditions, make sure you use exceptions only in exceptional circumstances when error conditions occur. Do not use exceptions for regular control flow.
抛出异常的性能代价是巨大的。尽管结构化异常处理是处理错误条件的推荐方式,但请确保只有在出现错误条件的异常情况下才使用异常。不要对常规控制流使用异常。
Of course, this is only for .NET, and it's also directed specifically at those developing high-performance applications (like myself); so it's obviously not a universal truth. Still, there are a lot of us .NET developers out there, so I felt it was worth noting.
当然,这只适用于。net,它还专门针对那些开发高性能应用程序的人(比如我自己);显然这不是一个普遍的真理。尽管如此,我们还是有很多的。net开发人员,所以我觉得值得注意。
EDIT:
编辑:
OK, first of all, let's get one thing straight: I have no intention of picking a fight with anyone over the performance question. In general, in fact, I am inclined to agree with those who believe premature optimization is a sin. However, let me just make two points:
好吧,首先,让我们弄清楚一件事:我无意就表演问题与任何人争论。总的来说,事实上,我倾向于同意那些认为过早优化是一种罪恶的观点。但是,我想说两点:
-
The poster is asking for an objective rationale behind the conventional wisdom that exceptions should be used sparingly. We can discuss readability and proper design all we want; but these are subjective matters with people ready to argue on either side. I think the poster is aware of this. The fact is that using exceptions to control program flow is often an inefficient way of doing things. No, not always, but often. This is why it's reasonable advice to use exceptions sparingly, just like it's good advice to eat red meat or drink wine sparingly.
这张海报在寻求一种客观的理由来支持常规的观点,即应该谨慎地使用例外。我们可以讨论可读性和适当的设计,我们想;但这些都是主观的问题,人们愿意在任何一方争论。我想海报已经意识到了这一点。事实上,使用异常来控制程序流通常是一种低效的方法。不,不总是,但经常。这就是为什么谨慎地使用异常是合理的建议,就像谨慎地吃红肉或喝酒一样。
-
There is a difference between optimizing for no good reason and writing efficient code. The corollary to this is that there's a difference between writing something that is robust, if not optimized, and something that is just plain inefficient. Sometimes I think when people argue over things like exception handling they're really just talking past each other, because they are discussing fundamentally different things.
无理由的优化和编写高效的代码之间是有区别的。由此得出的结论是,编写健壮(如果没有优化的话)的代码和编写效率很低的代码是有区别的。有时我认为当人们争论诸如异常处理之类的事情时他们实际上只是在讨论彼此,因为他们讨论的是根本不同的事情。
To illustrate my point, consider the following C# code examples.
为了说明我的观点,请考虑以下c#代码示例。
Example 1: Detecting invalid user input
This is an example of what I'd call exception abuse.
这是我称之为例外滥用的一个例子。
int value = -1;
string input = GetInput();
bool inputChecksOut = false;
while (!inputChecksOut) {
try {
value = int.Parse(input);
inputChecksOut = true;
} catch (FormatException) {
input = GetInput();
}
}
This code is, to me, ridiculous. Of course it works. No one's arguing with that. But it should be something like:
这段代码对我来说太荒谬了。当然它的工作原理。没人会反对。但它应该是这样的:
int value = -1;
string input = GetInput();
while (!int.TryParse(input, out value)) {
input = GetInput();
}
Example 2: Checking for the existence of a file
I think this scenario is actually very common. It certainly seems a lot more "acceptable" to a lot of people, since it deals with file I/O:
我认为这种情况很常见。对于很多人来说,它似乎更“可接受”,因为它处理文件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();
}
}
This seems reasonable enough, right? We're trying to open a file; if it's not there, we catch that exception and try to open a different file... What's wrong with that?
这似乎很合理,对吧?我们试图打开一个文件;如果它不在那里,我们捕获那个异常并尝试打开另一个文件……有什么问题吗?
Nothing, really. But consider this alternative, which doesn't throw any exceptions:
没什么,真的。但是考虑一下这个选项,它不会抛出任何异常:
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();
}
}
Of course, if the performance of these two approaches were actually the same, this really would be purely a doctrinal issue. So, let's take a look. For the first code example, I made a list of 10000 random strings, none of which represented a proper integer, and then added a valid integer string onto the very end. Using both of the above approaches, these were my results:
当然,如果这两种方法的表现实际上是一样的,这就纯粹是一个教条问题。我们来看看。对于第一个代码示例,我创建了一个包含10000个随机字符串的列表,其中没有一个字符串表示一个合适的整数,然后在末尾添加一个有效的整数字符串。使用上述两种方法,我的结果如下:
Using try
/catch
block: 25.455 seconds
Using int.TryParse
: 1.637 milliseconds
使用try/catch块:25.455秒使用int.TryParse: 1.637毫秒
For the second example, I did basically the same thing: made a list of 10000 random strings, none of which was a valid path, then added a valid path onto the very end. These were the results:
对于第二个示例,我基本上做了相同的事情:创建一个包含10000个随机字符串的列表,其中没有一个是有效的路径,然后在末尾添加一个有效的路径。这些结果:
Using try
/catch
block: 29.989 seconds
Using File.Exists
: 22.820 milliseconds
使用try/catch块:29.989秒使用文件。存在:22.820毫秒
A lot of people would respond to this by saying, "Yeah, well, throwing and catching 10,000 exceptions is extremely unrealistic; this exaggerates the results." Of course it does. The difference between throwing one exception and handling bad input on your own is not going to be noticeable to the user. The fact remains that using exceptions is, in these two case, from 1,000 to over 10,000 times slower than the alternative approaches that are just as readable -- if not more so.
很多人会这样回答:“嗯,投掷和捕捉10000个异常是非常不现实的;这夸大了结果。”当然它。抛出一个异常和自己处理错误输入之间的区别不会被用户注意到。事实仍然是,在这两种情况下,使用异常要比可读的其他方法慢1,000到10,000多倍——如果不是更慢的话。
That's why I included the example of the GetNine()
method below. It isn't that it's intolerably slow or unacceptably slow; it's that it's slower than it should be... for no good reason.
这就是为什么我在下面包含GetNine()方法的示例。并不是说它慢得让人无法忍受或者慢得让人无法接受;而是它比它应该的要慢……没有充分的理由。
Again, these are just two examples. Of course there will be times when the performance hit of using exceptions is not this severe (Pavel's right; after all, it does depend on the implementation). All I'm saying is: let's face the facts, guys -- in cases like the one above, throwing and catching an exception is analogous to GetNine()
; it's just an inefficient way of doing something that could easily be done better.
这只是两个例子。当然,有时使用异常的性能影响不是那么严重(Pavel是对的;毕竟,这取决于实现)。我想说的是:让我们面对事实——在上面的例子中,抛出和捕获一个异常类似于GetNine();这只是一种效率低下的方法,可以很容易地做得更好。
You are asking for a rationale as if this is one of those situations where everyone's jumped on a bandwagon without knowing why. But in fact the answer is obvious, and I think you know it already. Exception handling has horrendous performance.
你在问一个理由,就好像这是那种每个人都不知道为什么而随波逐流的情况。但事实上答案是显而易见的,我想你已经知道了。异常处理具有可怕的性能。
OK, maybe it's fine for your particularly business scenario, but relatively speaking, throwing/catching an exception introduces way more overhead than is necessary in many, many cases. You know it, I know it: most of the time, if you're using exceptions to control program flow, you're just writing slow code.
好的,对于您的特定业务场景来说,这也许是可以的,但是相对而言,抛出/捕获一个异常所带来的开销要比许多情况下需要的开销大得多。您知道,我知道:大多数情况下,如果您使用异常来控制程序流,那么您就是在编写慢代码。
You might as well ask: why is this code bad?
您可能还会问:为什么这段代码不好?
private int GetNine() {
for (int i = 0; i < 10; i++) {
if (i == 9) return i;
}
}
I would bet that if you profiled this function you'd find it performs quite acceptably fast for your typical business application. That doesn't change the fact that it's a horribly inefficient way of accomplishing something that could be done a lot better.
我敢打赌,如果您分析了这个函数,您会发现它对于典型的业务应用程序的执行速度非常快。这并不能改变一个事实,那就是这是一种非常低效的方式来完成一些可以做得更好的事情。
That's what people mean when they talk about exception "abuse."
这就是人们所说的例外“虐待”的意思。
#9
6
All of the rules of thumb about exceptions come down to subjective terms. You shouldn't expect to get hard and fast definitions of when to use them and when not to. "Only in exceptional circumstances". Nice circular definition: exceptions are for exceptional circumstances.
所有关于例外的经验法则都可以归结为主观因素。你不应该期望对何时使用它们以及何时使用它们有严格的定义。“只有在特殊的情况下”。良好的循环定义:异常是针对特殊情况的。
When to use exceptions falls into the same bucket as "how do I know whether this code is one class or two?" It's partly a stylistic issue, partly a preference. Exceptions are a tool. They can be used and abused, and finding the line between the two is part of the art and skill of programming.
什么时候使用异常与“我如何知道这段代码是一个类还是两个类?”这部分是风格问题,部分是偏好问题。异常是一种工具。它们可以被使用和滥用,找到两者之间的界线是编程艺术和技巧的一部分。
There are lots of opinions, and tradeoffs to be made. Find something that speaks to you, and follow it.
有很多观点,需要权衡。找到一些对你说话的东西,然后跟随它。
#10
6
It's not that exceptions should rarely be used. It's just that they should only be thrown in exceptional circumstances. For example, if a user enters the wrong password, that's not exceptional.
并不是说应该很少使用异常。只是他们应该被扔在特殊的环境中。例如,如果用户输入了错误的密码,这不是例外。
The reason is simple: exceptions exit a function abruptly, and propagate up the stack to a catch
block. This process is very computationally expensive: C++ builds its exception system to have little overhead on "normal" function calls, so when an exception is raised, it has to do a lot of work to find where to go. Moreover, since every line of code could possibly raise an exception. If we have some function f
that raises exceptions often, we now have to take care to use our try
/catch
blocks around every call of f
. That's a pretty bad interface/implementation coupling.
原因很简单:异常会突然退出一个函数,并将堆栈向上传播到一个catch块。这个过程在计算上非常昂贵:c++构建了它的异常系统,在“正常”函数调用上几乎没有开销,因此当出现异常时,它必须做大量工作才能找到要去的地方。此外,因为每一行代码都可能引发异常。如果我们有一个经常引起异常的函数f,我们现在必须注意在每次调用f时使用try/catch块,这是一个非常糟糕的接口/实现耦合。
#11
5
I mentioned this issue in an article on C++ exceptions.
我在一篇关于c++异常的文章中提到了这个问题。
The relevant part:
相关的部分:
Almost always, using exceptions to affect the "normal" flow is a bad idea. As we already discussed in section 3.1, exceptions generate invisible code paths. These code paths are arguably acceptable if they get executed only in the error handling scenarios. However, if we use exceptions for any other purpose, our "normal" code execution is divided into a visible and invisible part and it makes code very hard to read, understand and extend.
几乎总是,使用异常来影响“正常”流不是一个好主意。正如我们在第3.1节中已经讨论过的,异常生成不可见的代码路径。如果只在错误处理场景中执行这些代码路径,那么这些代码路径是可以接受的。然而,如果我们将异常用于任何其他目的,我们的“正常”代码执行被划分为可见的和不可见的部分,这使得代码很难阅读、理解和扩展。
#12
5
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.
- 在错误站点可以处理的奇怪情况。这可能是如果用户在命令行提示符中输入无效的输入。在这种情况下,正确的行为是向用户投诉并循环。另一种情况是被0除。这些情况不是真正的错误情况,通常是由错误的输入引起的。
- 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.
- 类似于前一种情况,但是不能在错误站点中处理。例如,如果有一个函数接收文件名并解析文件名,它可能无法打开该文件。在这种情况下,它不能处理错误。这是例外出现的时候。与使用C方法(返回无效值作为标志并设置全局错误变量来指示问题)不同,代码可以抛出一个异常。然后,调用代码将能够处理异常——例如,为另一个文件名提示用户。
- 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. - 不应该发生的情况。这是当类不变量被违反时,或者函数接收无效的参数或类似的情况。这表示代码中的逻辑错误。根据故障级别的不同,可能需要一个异常,或者最好是强制立即终止(就像assert所做的那样)。一般来说,这些情况表明代码中的某个地方出了问题,您不能信任其他任何东西是正确的——可能存在严重的内存损坏。你的船要沉了,快下来。
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.
换句话说,例外是指当你有问题可以解决,但你不能在你注意到的地方解决。你无法处理的问题应该简单地终止程序;你可以马上解决的问题应该简单地解决。
#13
5
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:
我在这里读到了一些答案。我仍然对这些困惑感到惊讶。我强烈反对所有这些异常=spagetty代码。我的意思是,有些人不喜欢c++异常处理。我不确定我是如何了解c++异常处理的,但我在几分钟内就理解了它的含义。这是在1996年左右,我在使用borland c++编译器的OS/2。我从来没有一个问题来决定,什么时候使用例外。我通常将易出错的do-undo操作打包到c++类中。这种do-undo行为包括:
- creating/destroying a system handle (for files, memory maps, WIN32 GUI handles, sockets, and so on)
- 创建/销毁一个系统句柄(用于文件、内存映射、WIN32 GUI句柄、套接字等)
- 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++. E.g. read/write from/to a file. If something fails, an exception will be thrown, which contains full information about the error.
而不是功能性包装。将系统调用(不属于前一类)封装到c++中。从一个文件中读/写。如果发生故障,将抛出异常,其中包含有关错误的完整信息。
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.
总的来说,c++异常处理会带来更清晰的代码。代码的数量急剧减少。最后,可以使用构造函数来分配易出错的资源,并在发生此类故障后仍然保持无损坏的环境。
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.
可以将此类类链接到复杂的类中。一旦执行了某个成员/基对象的构造函数,就可以依赖于同一对象的所有其他构造函数(在成功执行之前执行)。
#14
3
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.
与传统的结构(循环、ifs、函数等)相比,异常是一种非常不常见的流控制方法。正常的控制流结构(循环、ifs、函数调用等)可以处理所有正常的情况。如果您发现自己遇到了一个例程异常,那么您可能需要考虑代码的结构。
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 (e.g., 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语句是不够的。这些类型的失败通常需要在更高的级别上处理(例如,保存文件、记录错误、退出)。尝试通过传统的方法(如返回值)报告这样的错误是冗长且容易出错的。此外,它将开销注入中层api层,以处理这种奇怪的、不寻常的失败。开销分散了这些api的客户端,并要求他们担心超出他们控制范围的问题。异常提供了一种方法来对大错误进行非本地处理,这些错误在问题的检测和处理程序之间的所有层中通常是不可见的。
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.
如果客户端使用字符串调用ParseInt,而字符串不包含整数,那么直接调用者可能会关心错误并知道如何处理它。你可以设计ParseInt来返回类似的失败代码。
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).
另一方面,如果ParseInt失败了,因为它不能分配一个缓冲区,因为内存是非常分散的,那么调用者就不知道该怎么做。它将不得不把这个不寻常的错误推到某一层来处理这些根本的失败。这使得每个人(因为他们必须在自己的api中容纳错误传递机制)之间的税收。一个异常使跳过这些层成为可能(同时仍然确保进行必要的清理)。
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.
在编写低级代码时,很难决定什么时候使用传统方法,什么时候抛出异常。低级代码必须作出决定(抛出或不抛出)。但是高级代码才是真正知道什么是期望的,什么是例外的。
#15
3
There's several reasons in C++.
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.
首先,通常很难看到异常来自哪里(因为它们几乎可以从任何东西中抛出),因此catch块是来自语句的某种东西。它比GO TO更糟糕,因为在GO TO中你知道你从哪里来(语句,而不是某个随机的函数调用)和你要去哪里(标签)。它们基本上是C的setjmp()和longjmp()的一个潜在资源安全版本,没有人愿意使用它们。
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.
其次,c++没有内置的垃圾收集,所以拥有资源的c++类会在它们的析构函数中删除它们。因此,在c++异常处理中,系统必须运行范围内的所有析构函数。在使用GC而没有真正的构造函数(如Java)的语言中,抛出异常要容易得多。
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.
第三,c++社区(包括Bjarne Stroustrup和标准委员会以及各种编译器作者)一直假定异常应该是异常的。总的来说,它不值得违背语言文化。这些实现基于一个假设,即异常将是罕见的。更好的书把例外视为例外。好的源代码很少使用异常。优秀的c++开发人员将异常视为异常。与之相反的是,你需要一个好的理由,而我看到的所有理由都是为了让他们与众不同。
#16
2
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"
异常也有一些与性能相关的惩罚,但是性能问题应该遵循以下规则:“过早优化是万恶之源”
#17
2
- Maintainability: As mentioned by people above, throwing exceptions at a drop of a hat is akin to using gotos.
- 可维护性:正如上面提到的,在一滴水中抛出异常类似于使用goto。
- Interoperability: You can't interface C++ libraries with C/Python modules (atleast not easily) if you are using exceptions.
- 互操作性:如果您使用异常,您不能使用C/Python模块(至少不容易)与c++库交互。
- 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).
- 性能下降:RTTI用于实际查找异常的类型,这会增加额外的开销。因此,异常不适用于处理常见的用例(用户输入int而不是string等)。
#18
2
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.
我想说,异常是一种以安全的方式将您从当前上下文(从最简单的意义上说,从当前堆栈框架中取出,但它不止于此)中取出的机制。这是最接近结构化编程的东西。要以预期的方式使用异常,您必须有这样一种情况:您无法继续您现在正在做的工作,并且您无法在当前的位置处理它。例如,当用户密码错误时,可以继续返回false。但是如果UI子系统报告它甚至不能提示用户,那么简单地返回“登录失败”将是错误的。当前级别的代码根本不知道该做什么。因此,它使用异常机制将责任委托给可能知道该做什么的人。
#19
2
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".
一个非常实际的原因是,在调试程序时,我经常会抛出第一次机会异常(Debug ->异常)来调试应用程序。如果有很多例外发生,就很难找到哪里出了问题。
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.
此外,它还会导致一些反模式,如臭名昭著的“接球”和混淆真正的问题。有关这方面的更多信息,请参阅我关于这一主题的博客文章。
#20
2
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.
谷歌的编码策略规定永远不要使用异常,特别是在c++中。您的应用程序要么不准备处理异常,要么准备处理异常。如果它不是,那么异常可能会传播到您的应用程序死亡为止。
It's never fun to find out some library you have used throws exceptions and you were not prepared to handle them.
查找您使用过的抛出异常的库,并且您不准备处理它们,这从来都不是一件有趣的事情。
#21
1
Legitimate case to throw an exception:
有正当理由提出例外:
- You try to open a file, it's not there, a FileNotFoundException is thrown;
- 你尝试打开一个文件,它不在那里,一个FileNotFoundException被抛出;
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.
- 只有当文件不存在时,您才需要做一些事情,您尝试打开文件,然后向catch块添加一些代码。
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.
当我想要将应用程序的流分解到某个点时,我使用异常。这一点是异常的捕获(…)所在。例如,我们经常需要处理大量的项目,每个项目都应该独立于其他项目进行处理。因此,处理项目的循环需要尝试……catch块,如果在项目处理过程中抛出异常,则该项目的所有内容都回滚,并记录错误,然后处理下一个项目。生活还在继续。
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.
也许我的示例不是很好——在第二个示例中,不应该抛出一个异常,如果有,那么就会有一些异常,应该在其他地方采取其他操作。当示例1被大量使用时,开发人员将捕获一个通用异常而不是特定的异常,并假定该异常是由于他们预期的错误造成的,但它可能是由于其他原因造成的。最后,这会导致真正的异常被忽略,因为异常成为应用程序流的一部分,而不是异常。
The comments on this may add more than my answer itself.
对此的评论可能不仅仅是我的回答。
#22
0
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.
当您希望在调用者忽略编写错误处理代码的情况下提供一个正常的默认值时,或者如果错误最好是在调用堆栈中比直接调用者处理得更高级时,应该使用IMHO异常。正常的默认情况是退出带有合理诊断错误消息的程序。疯狂的替代方案是程序在错误的状态中蹒跚前进,崩溃或者在以后的某个时候悄无声息地产生糟糕的输出,这是很难诊断的。如果“错误”是程序流的正常部分,调用者无法合理地忘记检查它,那么就不应该使用异常。
#23
0
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.
原因是,异常不仅是非常强大的goto语句,它们还必须为它们所留下的所有帧展开堆栈。因此,栈上的对象也必然要被解构,等等。所以如果没有意识到这一点,一个异常的抛出就会涉及到一系列的力学问题。处理器需要做很多事情。
So you will end up, elegantly burning your processor without knowing.
所以你最终会在不知不觉中优雅地烧掉你的处理器。
So: use exceptions only in exceptional cases -- Meaning: When real errors occured!
所以:只在异常情况下使用异常——意思是:当出现真正的错误时!
#24
0
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.
我认为好的编程语言不支持异常,正如我们在c++和Java中所知道的那样。您应该选择可以为函数的各种返回值提供可选流的编程语言。程序员应该负责预测一个例程的所有形式的输出,并在一个独立的代码文件中处理它们。
#25
0
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.).
如果可以从(用户输入“apple”而不是数字)恢复错误,则恢复(再次请求输入,更改为默认值,等等)。
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.
如果不能从本地恢复错误,并且应用程序不能在不恢复的情况下继续运行(内存/磁盘空间/等等),那么异常是正确的方法。
#26
0
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 ?
谁说他们应该保守地使用?只是不要在流控制中使用异常。当异常已经抛出时,谁会关心异常的代价呢?
#27
0
My two cents:
我的两个美分:
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.
我喜欢使用异常,因为它允许我编程,就好像不会发生错误一样。因此,我的代码仍然是可读的,而不是散布在各种错误处理中。当然,错误处理(异常处理)被移动到末尾(catch块),或者被认为是调用级别的响应性。
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.
异常的问题是,它会很快变得非常冗长。虽然它的目的是让您的代码保持可读性,并且只关注正常的事物流,但是如果始终如一地使用,几乎每个函数调用都需要封装在try/catch块中,并且它开始破坏目的。
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
对于前面提到的ParseInt,我喜欢异常的概念。只是返回值。如果参数不可解析,则抛出异常。它让你的代码更简洁。在调用级别,您需要做一些类似的事情
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. E.g.
是干净的代码。当我得到这样分散的ParseInt时,我创建了处理异常并返回默认值的包装器函数。如。
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.
- 仍然需要在某些地方处理异常。额外问题:c++没有允许它指定函数可能抛出哪些异常的语法(像java那样)。因此调用级别不知道需要处理哪些异常。
- sometimes code can get very verbose, if every function needs to be wrapped in a try/catch block. But sometimes this still makes sense.
- 如果每个函数都需要在try/catch块中进行包装,那么有时代码会变得非常冗长。但有时这还是有道理的。
So that makes it hard to find a good balance sometimes.
所以有时候很难找到一个好的平衡。
#28
-1
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....
几乎所有的编程规则是一个指导方针说“别这样做,除非你有一个很好的理由”:“千万不要使用goto”,“避免全局变量”、“正则表达式pre-increment你许多问题通过一个“,等。异常也不例外....