问题描述
我的问题是大多数开发人员喜欢错误处理,异常或错误返回代码。请你是语言(或语言家庭)具体的,为什么你更喜欢一个。我是出于好奇问道。个人而言,我喜欢错误返回代码,因为它们的爆发力较小,如果不想要强制用户代码支付异常性能损失。
更新:谢谢为所有的答案!我必须说,尽管我不喜欢代码流与异常的不可预测性。关于返回代码(和他们的哥哥手柄)的答案确实给代码添加了大量的噪音。
对于某些语言即C ++)资源泄露不应该是原因
C ++基于RAII。
如果你有代码那可能会失败,返回或抛出(也就是大多数正常的代码),那么你应该把你的指针包含在一个智能指针中(假设你有一个非常好的原因,没有在堆栈上创建你的对象)
返回代码更详细
它们是冗长的,并且倾向于发展成如下: / p>
if(doSomething())
{
if(doSomethingElse())
{
if(doSomethingElseAgain())
{
//等
}
else
{
//对doSomethingElseAgain失败的响应
}
}
else
{
//对doSomethingElse的失败做出反应
}
}
else
{
//对doSomething的失败作出反应
}
最后,你的代码是一组标识的指令(我在生产代码中看到这样的代码)。
这段代码可以翻译成:
try
{
doSomething();
doSomethingElse();
doSomethingElseAgain();
}
catch(const SomethingException& e)
{
//对doSomething
的失败做出反应
catch(const SomethingElseException& e)
{
//对doSomethingElse
的失败做出反应
catch(const SomethingElseAgainException& e)
{
//对doSomethingElseAgain的失败做出反应
}
哪些干净的代码和错误处理,可以
返回代码更脆弱
如果不是一些一个编译器的模糊警告(参见phjr的注释),它们可以轻易被忽略。
上面的例子假设有人忘记处理它可能的错误(这发生...)。 返回时忽略该错误,稍后可能会发生错误(即NULL指针)。同样的问题不会发生例外。
错误不会被忽略。有时,你希望它不会爆炸,但是...所以你必须仔细选择。
有时需要转换代码
假设我们有以下功能:
- doSomething,它可以返回一个名为NOT_FOUND_ERROR的int
- doSomethingElse,它可以返回一个boolfalse(为失败)
- doSomethingElseSagain,它可以返回一个Error对象(同时包含__LINE__,__FILE__和一半的堆栈变量
- doTryToDoSomethingWithAllThisMess哪个,好...使用上述函数,并返回类型为...的
的错误代码
如果其中一个被调用的函数失败,那么doTryToDoSomethingWithAllThisMess返回的类型是什么?
返回代码不是通用解决方案
运算符不能返回错误代码,C ++构造函数也不能。
返回代码意味着您不能链接表达式
上述点的推论。如果我想写:
CMyType o = add(a,multiply(b,c));
我不能,因为返回值已经被使用了(有时它不能改变)。所以返回值成为第一个参数,作为引用发送...或者不是。
异常键入
您可以为每种异常发送不同的类。资源异常(即内存不足)应该是轻的,但是其他任何东西都可能是必要的(我喜欢Java异常给我整个堆栈)。
每个抓住可以专门的。
不要使用catch(...)而不重新投掷
异常是... NUKE
异常的问题是过度使用它们将产生充满try / catch的代码。但是问题在别的地方:谁使用STL容器尝试/抓住他/她的代码?不过,这些容器可能会发送异常。
当然,在C ++中,不要让异常退出析构函数。
异常是...同步
确保抓住它们,然后将线程拔出,或在Windows消息循环内传播。
解决方案可能是混合使用?
所以我猜解决方案是当某个东西应该强>不发生。而当某些事情发生时,请使用返回代码或参数来启用用户对其的反应。
所以唯一的问题是应该是什么不会发生?
这取决于你的功能的合同。如果函数接受一个指针,但是指定的指针必须是非空的,那么当用户发送一个NULL指针时,抛出一个异常是正确的(问题是C ++中没有函数使用引用的时候)的指针,但...)
另一个解决方案是显示错误
有时,你的问题是你不想要错误。使用例外或错误返回代码很酷,但...你想知道它。
在我的工作中,我们使用一种Assert。这将取决于配置文件的值,无论调试/发布编译选项如何:
- 记录错误
- 打开一个带有嘿,你有问题的消息框
- 打开一个带有嘿,你有问题,你想调试的消息框
在开发和测试中,这使用户能够在检测到问题时精确确定问题,而不是在(当某些代码关心返回值,或者在catch中)
很容易添加到旧代码。例如:
void doSomething(CMyObject * p,int iRandomData)
{
//等
}
导致类似于以下代码:
void doSomething(CMyObject * p,int iRandomData)
{
if(iRandomData< 32)
{
MY_RAISE_ERROR(Hey,iRandomData<< iRandomData<<小于32.中止处理);
返回;
}
if(p == NULL)
{
MY_RAISE_ERROR(嘿,p是NULL!\\\
iRandomData等于<< iRandomData <将抛出。);
throw std :: some_exception();
}
如果(!p.is Ok())
{
MY_RAISE_ERROR(嘿,p不好!\\\
p等于 ;< p-> toString()<&。将尝试继续继续);
}
//等
}
(我有类似的宏仅在调试中有效)。
请注意,在生产中,配置文件不存在,因此客户端从未看到此结果宏...但是在需要时很容易激活。
结论
当您使用return代码,你正在准备自己的失败,并希望你的测试堡垒足够安全。
当你使用异常代码时,你知道你的代码可能会失败,通常在您的代码中将选择的战略位置置于反火中。但是通常情况下,你的代码更多的是它必须做什么,然后我会害怕会发生什么。
但是,当你编写代码时,你必须使用最好的工具在你的处置,有时,它是永远不会隐藏错误,并尽快显示。我上面讲的宏观遵循这一理念。
My question is what do most developers prefer for error handling, Exceptions or Error Return Codes. Please be language(or language family) specific and why you prefer one over the other.
I'm asking this out of curiosity. Personally I prefer Error Return Codes since they are less explosive and don't force user code to pay the exception performance penalty if they don't want to.
update: thanks for all the answers! I must say that although I dislike the unpredictability of code flow with exceptions. The answer about return code (and their elder brother handles) do add lots of Noise to the code.
For some languages (i.e. C++) Resources leak should not be a reason
C++ is based on RAII.
If you have code that could fail, return or throw (that is, most normal code), then you should have your pointer wrapped inside a smart pointer (assuming you have a very good reason to not have your object created on stack).
Return codes are more verbose
They are verbose, and tend to develop into something like:
if(doSomething())
{
if(doSomethingElse())
{
if(doSomethingElseAgain())
{
// etc.
}
else
{
// react to failure of doSomethingElseAgain
}
}
else
{
// react to failure of doSomethingElse
}
}
else
{
// react to failure of doSomething
}
In the end, you code is a collection of idented instructions (I saw this kind of code in production code).
This code could well be translated into:
try
{
doSomething() ;
doSomethingElse() ;
doSomethingElseAgain() ;
}
catch(const SomethingException & e)
{
// react to failure of doSomething
}
catch(const SomethingElseException & e)
{
// react to failure of doSomethingElse
}
catch(const SomethingElseAgainException & e)
{
// react to failure of doSomethingElseAgain
}
Which cleanly separate code and error processing, which can be a good thing.
Return codes are more brittle
If not some obscure warning from one compiler (see "phjr" 's comment), they can easily be ignored.
With the above examples, assume than someone forgets to handle its possible error (this happens...). The error is ignored when "returned", and will possibly explode later (i.e. a NULL pointer). The same problem won't happen with exception.
The error won't be ignored. Sometimes, you want it to not explode, though... So you must chose carefully.
Return Codes must sometimes be translated
Let's say we have the following functions:
- doSomething, which can return an int called NOT_FOUND_ERROR
- doSomethingElse, which can return a bool "false" (for failed)
- doSomethingElseSagain, which can return an Error object (with both the __LINE__, __FILE__ and half the stack variables.
- doTryToDoSomethingWithAllThisMess which, well... Use the above functions, and return an error code of type...
What is the type of the return of doTryToDoSomethingWithAllThisMess if one of its called functions fail ?
Return Codes are not an universal solution
Operators cannot return an error code. C++ constructors can't, too.
Return Codes means you can't chain expressions
The corollary of the above point. What if I want to write:
CMyType o = add(a, multiply(b, c)) ;
I can't, because the return value is already used (and sometimes, it can't be changed). So the return value becomes the first parameter, sent as a reference... Or not.
Exception are typed
You can send different classes for each kind of exception. Ressources exceptions (i.e. out of memory) should be light, but anything else could be as heavy as necessary (I like the Java Exception giving me the whole stack).
Each catch can then be specialized.
Don't ever use catch(...) without re-throwing
Usually, you should not hide an error. If you do not re-throw, at the very least, log the error in a file, open a messagebox, whatever...
Exception are... NUKE
The problem with exception is that overusing them will produce code full of try/catches. But the problem is elsewhere: Who try/catch his/her code using STL container? Still, those containers can send an exception.
Of course, in C++, don't ever let an exception exit a destructor.
Exception are... synchronous
Be sure to catch them before they bring out your thread on its knees, or propagate inside your Windows message loop.
The solution could be mixing them?
So I guess the solution is to throw when something should not happen. And when something can happen, then use a return code or a parameter to enable to user to react to it.
So, the only question is "what is something that should not happen?"
It depends on the contract of your function. If the function accepts a pointer, but specifies the pointer must be non-NULL, then it is ok to throw an exception when the user sends a NULL pointer (the question being, in C++, when didn't the function author use references instead of pointers, but...)
Another solution would be to show the error
Sometimes, your problem is that you don't want errors. Using exceptions or error return codes are cool, but... You want to know about it.
In my job, we use a kind of "Assert". It will, depending on the values of a configuration file, no matter the debug/release compile options:
- log the error
- open a messagebox with a "Hey, you have a problem"
- open a messagebox with a "Hey, you have a problem, do you want to debug"
In both development and testing, this enable the user to pinpoint the problem exactly when it is detected, and not after (when some code cares about the return value, or inside a catch).
It is easy to add to legacy code. For example:
void doSomething(CMyObject * p, int iRandomData)
{
// etc.
}
leads a kind of code similar to:
void doSomething(CMyObject * p, int iRandomData)
{
if(iRandomData < 32)
{
MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ;
return ;
}
if(p == NULL)
{
MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ;
throw std::some_exception() ;
}
if(! p.is Ok())
{
MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ;
}
// etc.
}
(I have similar macros that are active only on debug).
Note that on production, the configuration file does not exist, so the client never sees the result of this macro... But it is easy to activate it when needed.
Conclusion
When you code using return codes, you're preparing yourself for failure, and hope your fortress of tests is secure enough.
When you code using exception, you know that your code can fail, and usually put counterfire catch at chosen strategic position in your code. But usually, your code is more about "what it must do" then "what I fear will happen".
But when you code at all, you must use the best tool at your disposal, and sometimes, it is "Never hide an error, and show it as soon as possible". The macro I spoke above follow this philosophy.
这篇关于哪个,为什么,你喜欢异常或退货代码?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!