C++ 为什么要保守地使用例外?
我经常看到/听到人们说,例外情况应该很少使用,但从不解释原因。虽然这可能是真的,但基本原理通常是油嘴滑舌的:“它被称为是有原因的例外”,在我看来,这似乎是一种永远不应该被一个值得尊敬的程序员/工程师接受的解释 异常可以用来解决一系列问题为什么将它们用于控制流是不明智的?在如何使用它们的问题上格外保守,其背后的理念是什么?语义学?演出复杂性?美学惯例? 我以前看过一些关于性能的分析,但分析的层次与某些系统相关,而与其他系统无关C++ 为什么要保守地使用例外?,c++,exception,C++,Exception,我经常看到/听到人们说,例外情况应该很少使用,但从不解释原因。虽然这可能是真的,但基本原理通常是油嘴滑舌的:“它被称为是有原因的例外”,在我看来,这似乎是一种永远不应该被一个值得尊敬的程序员/工程师接受的解释 异常可以用来解决一系列问题为什么将它们用于控制流是不明智的?在如何使用它们的问题上格外保守,其背后的理念是什么?语义学?演出复杂性?美学惯例? 我以前看过一些关于性能的分析,但分析的层次与某些系统相关,而与其他系统无关 同样,我并不一定不同意它们应该被保存在特殊情况下,但我想知道共识的基本
同样,我并不一定不同意它们应该被保存在特殊情况下,但我想知道共识的基本原理是什么(如果存在这样的事情)。主要的摩擦点是语义。许多开发人员滥用异常,并利用每一个机会抛出它们。其思想是在某种特殊情况下使用异常。例如,错误的用户输入不会被视为异常,因为您希望这种情况发生,并做好了准备。但如果您试图创建一个文件,但磁盘上没有足够的空间,那么是的,这是一个明确的例外 另一个问题是异常经常被抛出和吞并。开发人员使用这种技术只是“沉默”程序,让它尽可能长时间地运行,直到完全崩溃。这是非常错误的。如果您没有处理异常,如果您没有通过释放一些资源做出适当的反应,如果您没有记录异常发生或至少没有通知用户,那么您就没有使用异常来解释它们的含义 直接回答你的问题。很少使用异常,因为异常情况非常罕见,而且异常代价高昂 很少见,因为你不希望你的程序在每次按下按钮或每次错误的用户输入时崩溃。比如说,数据库可能突然无法访问,磁盘上可能没有足够的空间,您所依赖的某些第三方服务处于脱机状态,这一切都可能发生,但很少发生,这些都是明显的例外情况 昂贵,因为引发异常将中断正常的程序流。运行时将展开堆栈,直到找到可以处理异常的适当异常处理程序。它还将在传递给处理程序将接收的异常对象的过程中收集调用信息。这一切都是有代价的 这并不是说使用异常(微笑)没有例外。有时,如果抛出异常而不是通过多个层转发返回代码,则可以简化代码结构。作为一个简单的规则,如果您希望经常调用某个方法,并且有一半的时间发现某些“异常”情况,那么最好找到另一个解决方案。然而,如果您在大多数情况下都期望正常的操作流,而这种“异常”情况只能在一些罕见的情况下出现,那么抛出异常就可以了 @注释:如果异常可以使代码更简单、更容易,那么在一些不太异常的情况下肯定可以使用异常。这个选项是开放的,但我认为在实践中它是非常罕见的 为什么用它们来控制流量是不明智的 因为异常会破坏正常的“控制流”。如果引发异常,程序的正常执行将被放弃,可能会使对象处于不一致的状态,并且某些打开的资源将被取消绑定。当然,C#有一个using语句,它将确保即使从using主体抛出异常,也会释放该对象。但让我们暂时从语言中抽象出来。假设框架不会为您处理对象。你可以手工做。你有一些关于如何请求和释放资源和内存的系统。您与负责在什么情况下释放对象和资源的系统范围内的人员达成了一致。您有如何处理外部库的规则。如果程序遵循正常的操作流程,它将非常有效。但是突然在执行过程中抛出一个异常。一半的资源没有得到利用。一半还没有被要求。如果操作本来是事务性的,那么现在它就被破坏了。处理资源的规则将不起作用,因为那些负责释放资源的代码部分根本不会执行。如果其他任何人想要使用这些资源,他们可能会发现它们处于不一致的状态,也会崩溃,因为他们无法预测这种特殊情况 比方说,您需要一个方法M()调用方法N()来做一些工作并安排一些资源,然后将其返回给M(),M()将使用它,然后对其进行处理。好的现在N()中出现了一些错误,它抛出了一个在M()中没有预料到的异常,因此异常冒泡到顶部,直到它可能被某个方法C()捕获,该方法将不知道N()中到底发生了什么,以及是否以及如何释放一些资源
通过抛出异常,您可以创建一种方法,使您的程序进入许多难以预测、理解和处理的新中间状态。这有点类似于使用GOTO。很难设计一个程序,它可以随机地从一个位置跳到另一个位置执行。维护和调试它也很困难。当程序变得越来越复杂时,你只会对何时何地发生的事情失去一个大致的了解,更不用说修复它了。抛出异常在某种程度上类似于goto语句。这样做的流量控制,你结束了难以理解的意大利面条代码。更糟糕的是,在某些情况下,您甚至不知道跳转的确切位置(即,如果您在给定上下文中没有捕获异常)。这公然违反了提高维护效率的“最少意外”原则
int getTotalIncome(int incomeType) {
int totalIncome= 0;
try {
totalIncome= calculateIncomeAsTypeA();
} catch (IncorrectIncomeTypeException& e) {
totalIncome= calculateIncomeAsTypeB();
}
return totalIncome;
}
int getTotalIncome(int incomeType) {
int totalIncome= 0;
if (incomeType == A) {
totalIncome= calculateIncomeAsTypeA();
} else if (incomeType == B) {
totalIncome= calculateIncomeAsTypeB();
}
return totalIncome;
}
string s = "abc";
if (s.memory_allocation_succeeded()) {
do_something_with(s); // etc.
}
// 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';
}
# 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
int value = -1;
string input = GetInput();
bool inputChecksOut = false;
while (!inputChecksOut) {
try {
value = int.Parse(input);
inputChecksOut = true;
} catch (FormatException) {
input = GetInput();
}
}
int value = -1;
string input = GetInput();
while (!int.TryParse(input, out value)) {
input = GetInput();
}
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();
}
}
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();
}
}
private int GetNine() {
for (int i = 0; i < 10; i++) {
if (i == 9) return i;
}
}
RecordIterator<MyObject> ri = createRecordIterator();
try {
MyObject myobject = ri.next();
} catch(NoSuchElement exception) {
// Object doesn't exist, will create it
}
RecordIterator<MyObject> ri = createRecordIterator();
if (ri.hasNext()) {
// It exists!
MyObject myobject = ri.next();
} else {
// Object doesn't exist, will create it
}
try
{
b = ParseInt(some_read_string);
}
catch (ParseIntException &e)
{
// use some default value instead
b = 0;
}
int ParseIntWithDefault(String stringToConvert, int default_value=0)
{
int result = default_value;
try
{
result = ParseInt(stringToConvert);
}
catch (ParseIntException &e) {}
return result;
}