我花了几个小时阅读关于何时使用例外的SO问题,似乎有两个阵营有不同的观点:
这只是一个有争议的话题,没有被广泛接受的最佳实践吗?
答案 0 :(得分:12)
正如你可能从丰富的答案中收集的那样,肯定没有达成共识。
从语义上讲,异常和错误提供了完全相同的功能。事实上,它们在所有语义方面都是相同的,并且错误可以像异常一样被任意丰富(您不必使用简单的代码,您可以使用真正的数据包!)。
唯一的区别是它们的传播方法:
另一方面:
这两个解决方案显得笨拙的原因很简单,错误检查很难。事实上,我每天写的大部分代码都涉及错误检查,无论是技术还是功能。
那该怎么办?
警告:提前演示,如果只关心答案,请跳到下一部分
我个人喜欢在这里利用类型系统。典型的例子是指针引用二分法:指针就像一个可以为null的引用(并重新设置,但这里没关系)
因此,而不是:
// 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;
或者更好的是,使用聪明的指针:
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);
在这里,我发现使用异常是多余的,原因有两个:
我本身没有异常问题,但是他们可以使代码变得尴尬,请考虑:
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
}
并将其与“例外”案例进行比较:
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
}
在这种情况下,使用异常似乎不合适:/
另一方面:
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*/ }
那么最好的是什么?
这取决于。像所有工程问题一样,没有银弹,这都是关于让步的。
所以请记住两件事:
如果您发现自己想知道是否使用例外,请尝试使用您的API。如果没有明确的赢家,那就是:没有理想的解决方案。
哦,当显然在制作它时选择的错误报告机制不再合适时,请不要犹豫重构您的API。不要感到羞耻:需求随时间而变化,因此API随之变化是正常的。
个人我倾向于仅对不可恢复的错误使用异常:因此我的代码中只有少量try / catch,只有在最外层才能准确记录错误(爱堆栈框架)和日志也转储了BOM。
这与Haskell非常相似(并且确实受到强烈影响),代码分为两个明确的部分:任何可以抛出异常,只有IO部分(extern部分)可能实际捕获它们。因此,纯部分必须以其他方式处理错误条件,以防它们“正常”。
但是,如果我遇到一个问题,即使用异常会使代码更容易阅读,而且自然(这是主观的),那么我会使用例外:)
答案 1 :(得分:10)
我认为这不是C ++社区独有的讨论,但这里有两个高级指导方针对我有所帮助:
答案 2 :(得分:8)
异常比错误代码更容易使用,因为它们可以被深度嵌套的子例程抛出,只能在可以处理它的级别截获。
错误代码需要在链中向上传递,因此每个调用另一个函数的函数都必须将错误代码传递回其调用者。
错误代码与异常没有任何明显的功能优势,因为异常可以在其中捆绑错误代码。可能有一段时间错误代码可能比异常更有效,但我认为额外代码的成本和维护它的难度超过了任何可能的优势。
但是,许多应用程序中存在错误代码,因为它们是在没有异常的语言中编写或移植的,因此继续使用统一的错误处理方法是有意义的。
答案 3 :(得分:8)
不,但是没有达成共识。
编辑:正如您所看到的,从其他答案中可以看出,没有达成共识 - 只有思想流派,设计原则和行动计划。没有任何计划适合所有情况。
答案 4 :(得分:4)
即使达成共识,也不意味着它是有效的。您在此主要考虑的是实施和绩效成本。这些都是对项目和最终计划的真正限制。在这方面,有些事情需要考虑。
在运行时,传播的异常比简单的返回值更昂贵。这通常是在特殊情况下参数的来源。如果您在遭受性能损失时抛出了很多例外情况。 不要将此视为异常缓慢,它们仍然可以非常高效地实施,但仍然比错误代码更昂贵。
例外可以携带比错误代码更多的信息。类似于boost::exception
库的东西允许标记信息,这些信息可以在异常链上提供大量有用信息。不像一个简单的错误代码说文件未找到,异常可以携带文件名,试图加载它的模块,当然还有底层错误代码。这种类型的信息很难通过堆栈中的错误代码传播。
错误代码传播在实现时可能很麻烦。任何不想处理错误的函数都必须将值传递得更高。通常你会发现代码只是忽略了错误,因为程序员当时无法处理它,或者他不想重构他的函数签名来传递错误。
异常catch
语法很笨重。检查if
语句中的返回代码通常要比编写catch
块更容易。如果函数必须在不同的点捕获太多不同的异常,代码的含义将在大括号和try / catch子句中丢失。以下是一个概念的可能根源:如果函数调用通常会失败,则返回代码可能比异常更好。
清楚地了解例外工作将如何帮助您做出决定。有性能方面的考虑因素,但对于许多项目来说这可以忽略不计。有关异常安全的担忧,你必须知道使用异常是安全和不安全的。如果人们开始从函数中做短回报,错误代码也会有相同的安全问题。
了解团队的背景也可以提供帮助;考虑一下你是否有两个新的C ++程序员,一个来自C背景,另一个来自Java。有些人对错误代码感到满意,有些人则有例外情况。在每个方向上推动的距离都会影响您的项目并有助于提高整体质量。
最后没有明确的答案。虽然有些情况下人们可能明显胜过另一方,但这在很大程度上取决于项目。查看您的代码并在适当的时候继续重构。有时你甚至不会选择使用什么。其他时候它没有任何区别。它具有高度的项目特定性。
答案 5 :(得分:3)
肯定没有达成共识。在高级别,我的口头禅是应该在“例外情况”中使用异常 - 也就是说,这些情况不是程序员错误的结果,而是执行环境中不可预测的情况。
围绕这个问题存在大量争论。根据您在编写程序时参与的其他实践 - 例如RAII - 异常可能或多或少地在惯用方面有用。
答案 6 :(得分:1)
例外是唯一 正确报告构造函数错误的方法,所以选择很明显。抛出异常会强制调用者捕获它并以某种方式处理它。使用init()
方法返回错误代码的空构造函数的替代设计容易出错,并且可能导致状态未知的对象。
答案 7 :(得分:1)
“C ++社区”有派系,每个派别都有自己对异常的看法。
我在视频游戏行业工作的地方,在视频游戏系统上运行的代码中禁止例外。这是因为异常消耗CPU时间或内存,视频游戏系统都没有任何备用。
此外,在电子游戏中,很少有失败案例可以保证恢复正常;如果视频游戏失败,人类的生命或财产通常不会受到威胁。
答案 8 :(得分:1)
我喜欢Boost的Error and Exception Handling指南。基本上,在需要堆栈展开时使用异常。这很简单,也很明确。
答案 9 :(得分:1)
没有达成共识,但正如回复所表明的那样,许多人坚持认为只有在特殊情况下才会抛弃。我不太喜欢这个建议,因为那时问题就变成了“什么是特殊情况?”。我更喜欢以下建议
当且仅当替代方案未能满足后置条件(包括所有不变量作为隐式后置条件)时,函数必须抛出异常。
是否在代码中编写throw
的实现决定然后与函数的后置条件的设计决策相关联。
参见Herb Sutter和Andrei Alexandrescu的 C ++编码标准,第70项(区分错误和非错误)和72(更喜欢使用例外报告错误)。
答案 10 :(得分:0)
我没有在任何其他回复中看到这个问题,但是(如“有效Java”一书中所述)在进行OOP时,跳过抛出异常的可能性是在对象中有一个帮助方法来“询问”是否可以进行的操作。
例如Iterator或Scanner上的hasNext()方法,在调用相应的下一个*()方法之前通常称为。