C++ 社区是否就何时应该使用异常达成普遍共识?
我只花了几个小时通读了关于何时使用异常的问题,似乎有两个不同观点的阵营:
I just spent a few hours reading through SO questions on the topic of when to use exceptions, and it seems like there are two camps with different point of views:
- 对错误代码使用异常
- 大多数时候使用错误代码,只有在发生一些灾难性错误时才使用异常
这只是一个有争议的话题,没有被广泛接受的最佳实践吗?
Is this just a controversial topic with no widely accepted best practice?
推荐答案
正如您可能从大量答案中收集到的那样,肯定没有达成共识.
As you can probably gather from the wealth of answers, there is certainly no consensus.
在语义上,异常和错误提供完全相同的功能.实际上,它们在所有语义方面都是相同的,并且错误可以像异常一样任意丰富(您不必使用简单的代码,您可以使用真正的数据包!).
Semantically, exceptions and error provide the exact same functionality. Indeed they are identical in about all semantic aspects, and errors can be arbitrarily enriched much like exceptions (you don't have to use a simple code, you can use a real bundle of data!).
唯一的区别是它们的传播方法:
The only difference there is is their propagation methods:
- 必须手动传递错误
- 异常会自动传播
另一方面:
- 签名中完美地记录了错误的可能性
- 异常在代码检查中沉默(阅读GotW #20:代码复杂性和哭泣)和隐藏的执行路径使推理变得更加困难.
- the possibility of an error is perfectly documented in the signature
- exceptions are silent on code inspection (read GotW #20: Code Complexity and cry) and hidden paths of execution make reasoning harder.
这两种解决方案看起来笨拙的原因很简单,就是错误检查很困难.事实上,我每天编写的大部分代码都涉及错误检查,无论是技术性的还是功能性的.
The very reason both solutions can appear clunky is simply that error checking is difficult. Indeed most of the code I am writing daily concerns error checking, whether technical or functional.
那该怎么办?
警告:提前演示,如果您只关心答案,请跳到下一部分
我个人喜欢在这里利用类型系统.典型的例子是指针引用二分法:指针就像一个可以为空的引用(并重新设置,但在这里无关紧要)
I personally like to leverage the type system here. The typical example is the pointer-reference dichotomy: a pointer is like a reference that can be null (and reseated, but it does not matter here)
因此,代替:
// Exceptions specifications are better not used in C++
// Those here are just to indicate the presence of exceptions
Object const& Container::search(Key const& key) const throw(NotFound);
我会倾向于写:
Object const* Container::search(Key const& key) const;
或者更好的是,使用聪明的指针:
Or better yet, using clever pointers:
Pointer<Object const> Container::search(Key const& key) const;
template <typename O>
O* Pointer<O>::operator->() const throw(Null);
template <typename O>
O& Pointer<O>::operator*() const throw(Null);
在这里我发现异常的使用是多余的,原因有两个:
Here I find the use of exception superfluous for 2 reasons:
- 如果我们正在搜索一个对象,那么找不到它是一个非常普遍的现象,并且没有太多数据可携带:错误原因?它不在那里
- 客户并不一定认为它不存在是错误的,我凭什么认为我比她更了解她的业务?我是谁来决定永远不会出现找不到所需内容的情况?
- If we are searching for an object, then there not finding it is both a perfectly common occurrence and there is not much data to carry about: cause of error ? it is not there
- The client does not necessarily consider it an error that it is not there, who am I to assume that I know her business better than she does ? Who am I to decide that there will never be a case where it won't be appropriate not to find what was asked for ?
我对异常本身没有问题,但它们会使代码变得笨拙,请考虑:
I don't have a problem with exceptions per se, but they can make the code awkward, consider:
void noExceptions(Container const& c)
{
Pointer<Object const> o = c.search("my-item");
if (!o) {
o = c.search("my-other-item");
}
if (!o) { return; } // nothing to be done
// do something with o
}
并将其与异常"情况进行比较:
And compare it with the "exception" case:
void exceptions(Container const& c)
{
Object const* p = 0;
try {
p = &c.search("my-item");
}
catch(NotFound const&) {
try {
p = &c.search("my-other-item");
}
catch(NotFound const&) {
return; // nothing to be done
}
}
// do something with p
}
在这种情况下,使用异常似乎并不合适:/
In this case, the use of exceptions does not seem appropriate :/
另一方面:
try {
print() << "My cute little baby " << baby.name() << " weighs " << baby.weight();
}
catch(Oupsie const&) {
// deal
}
肯定比:
if (!print("My cute little baby ")) { /*deal*/ }
if (!print(baby.name())) { /*deal*/ }
if (!print(" weighs ")) { /*deal*/ }
if (!print(baby.weight())) { /*deal*/ }
<小时>
什么是最好的?
这取决于.像所有工程问题一样,没有灵丹妙药,一切都与让步有关.
It depends. Like all engineering problem there is no silver bullet, it's all about concessions.
所以请记住两件事:
- 错误报告是 API 的一部分
- API 的设计应该考虑到易用性
如果您发现自己想知道是否使用异常,请尝试使用您的 API.如果没有明确的赢家,那就是:没有理想的解决方案.
If you find yourself wondering whether to use an exception or not, just try to use your API. If there is no clear cut winner, it is just that: there is no ideal solution.
哦,当很明显在制作 API 时选择的错误报告机制不再合适时,请不要犹豫重构您的 API.不要感到羞耻:需求会随着时间的推移而变化,因此 API 随之变化是正常的.
Oh, and do not hesitate to refactor your API when it becomes clear that the error reporting mechanism elected at the time of crafting it is no longer appropriate. Don't be ashamed: requirements change with time, so it is normal that the API change with them.
就个人而言我倾向于仅将异常用于不可恢复的错误:因此,我的代码中很少有 try/catch,仅在最外层,以准确记录错误(喜欢堆栈帧)并记录也是 BOM 的转储.
Personally I tend to use exceptions for unrecoverable errors only: I therefore have few try/catch in my code, only in the outermost levels, to accurately log the error (love stack frames) and log a dump of the BOM as well.
这与 Haskell 非常相似(并且确实受到强烈影响),那里的代码分为两个清晰的部分:虽然任何部分都可以抛出异常,但只有 IO 部分(extern 部分)才能真正捕获它们.因此,纯部分必须以其他方式处理错误情况,以防它们正常".
This is very similar (and indeed strongly influenced) by Haskell, the code there is seggregated in two clear cut parts: while any can throw exceptions, only the IO part (the extern one) may actually catch them. Therefore, the pure part must deal with error conditions with other ways in case they are "normal".
但是,如果我面临使用异常使代码更易于阅读和自然(这是主观的)的问题,那么我使用异常:)
If, however, I am faced with a problem where using an exception makes the code easier to read and more natural (which is subjective) then I use an exception :)
相关文章