我的问题是大多数开发人员更喜欢错误处理,异常或错误返回代码.请具体说明语言(或语言家族)以及为什么您喜欢其中一种语言.
我出于好奇而问这个问题.我个人更喜欢错误返回代码,因为它们不那么具有爆炸性,并且如果不想要,也不会强制用户代码支付异常性能损失.
更新:感谢所有答案!我必须说,虽然我不喜欢代码流与异常的不可预测性.关于返回代码(以及他们的哥哥句柄)的答案会给代码添加大量的噪音.
C++基于RAII.
如果您的代码可能会失败,返回或抛出(也就是大多数普通代码),那么您应该将指针包装在智能指针内(假设您有充分的理由不在堆栈上创建对象).
返回代码更详细它们很冗长,并且倾向于发展成类似的东西:
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 }
最后,您的代码是一组精心指令(我在生产代码中看到了这种代码).
这段代码可以很好地翻译成:
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 }
这干净分开代码和错误处理,这可以是一个很好的事情.
退货代码更脆弱如果不是来自一个编译器的一些模糊警告(参见"phjr"的评论),它们很容易被忽略.
假设有人忘记处理可能的错误(这种情况发生......)."返回"时忽略该错误,稍后可能会爆炸(即NULL指针).异常不会发生同样的问题.
错误不会被忽略.有时,你希望它不会爆炸,但是......所以你必须谨慎选择.
有时必须翻译返回代码假设我们有以下功能:
doSomething,它可以返回一个名为NOT_FOUND_ERROR的int
doSomethingElse,可以返回bool"false"(失败)
doSomethingElseSagain,它可以返回一个Error对象(包含__LINE __,__ FILE__和一半堆栈变量.
doTryToDoSomethingWithAllThisMess,嗯......使用上面的函数,并返回类型错误代码...
如果其中一个被调用的函数失败,doTryToDoSomethingWithAllThisMess的返回类型是什么?
返回代码不是通用的解决方案操作员无法返回错误代码.C++构造函数也不能.
返回代码意味着您不能链接表达式上述观点的必然结果.如果我想写怎么办:
CMyType o = add(a, multiply(b, c)) ;
我不能,因为已经使用了返回值(有时候,它无法更改).所以返回值成为第一个参数,作为参考发送......或者不是.
键入例外您可以为每种异常发送不同的类.Ressources异常(即内存不足)应该很轻,但其他任何东西都可能是必要的(我喜欢Java Exception给我整个堆栈).
然后每个捕获物都可以专门化.
不要在没有重新投掷的情况下使用catch(...)通常,您不应该隐藏错误.如果你不重新投掷,至少,将错误记录在一个文件中,打开一个消息框,无论如何......
例外是...... NUKE异常的问题是过度使用它们会产生充满try/catches的代码.但问题出在其他地方:谁使用STL容器尝试/捕获他/她的代码?但是,这些容器可以发送异常.
当然,在C++中,不要让异常退出析构函数.
例外是...同步一定要抓住它们,然后再将它们放在膝盖上,或者在Windows消息循环中传播.
解决方案可能是混合它们?所以我想解决的办法就是扔东西的时候应该不会发生.当某些事情发生时,然后使用返回代码或参数来使用户能够对其做出反应.
所以,唯一的问题是"什么是不应该发生的事情?"
这取决于你的功能合同.如果函数接受指针,但指定指针必须是非NULL,那么当用户发送NULL指针时可以抛出异常(问题是,在C++中,函数作者没有使用引用时指针,但......)
另一种解决方案是显示错误有时,您的问题是您不想要错误.使用异常或错误返回代码很酷,但是......你想知道它.
在我的工作中,我们使用了一种"断言".无论调试/发布编译选项如何,它都将取决于配置文件的值:
记录错误
用"嘿,你有问题"打开一个消息框
用"嘿,你有问题,你想调试"打开一个消息框
在开发和测试中,这使用户能够在检测到问题时精确查明问题,而不是在(某些代码关心返回值或陷阱内)之后.
可以轻松添加到旧代码中.例如:
void doSomething(CMyObject * p, int iRandomData) { // etc. }
引出一种类似于以下的代码:
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. }
(我有类似的宏只在调试时有效).
请注意,在生产时,配置文件不存在,因此客户端永远不会看到此宏的结果......但是在需要时很容易激活它.
结论当您使用返回代码进行编码时,您正在为失败做好准备,并希望您的测试堡垒足够安全.
当您使用异常进行编码时,您知道您的代码可能会失败,并且通常会将反火箭捕获到代码中选定的战略位置.但通常,你的代码更多的是"它必须做什么"然后"我担心会发生什么".
但是当您编写代码时,您必须使用最好的工具,有时候,它是"永远不会隐藏错误,并尽快显示".我上面谈到的宏观遵循这一理念.
我实际上都用了.
如果是已知的可能错误,我会使用返回码.如果这是我知道可能会发生的情况,那么会有一个代码被发回.
例外仅用于我不期望的事情.
根据框架设计指南中的第7章"异常" :可重用.NET库的约定,惯用语和模式,给出了为什么对于诸如C#的OO框架使用异常而不是返回值的原因.
也许这是最令人信服的理由(第179页):
"例外用面向对象的语言很好地集成.面向对象的语言倾向于强加不是由在非面向对象的语言中的函数施加构件签名的约束.例如,在构造函数,运算符重载和属性的情况下,显影剂在返回值别无选择.出于这个原因,它是不可能对面向对象的框架为基础的返回值的错误报告规范.错误报告方法,如例外,这是该方法的签名的带是唯一的选择. "
我的偏好(在C++和Python中)是使用异常.语言提供的工具使其成为一个定义良好的过程,既可以提升,捕获和(如有必要)重新抛出异常,使模型易于查看和使用.从概念上讲,它比返回代码更清晰,因为特定的例外可以通过其名称来定义,并附带其他信息.使用返回代码,您仅限于错误值(除非您要定义ReturnStatus对象或其他内容).
除非您编写的代码对时间要求严格,否则与展开堆栈相关的开销并不足以让人担心.
只有在您不期望发生的事情发生时,才应返回例外情况.
从历史上看,另一个例外是返回代码本质上是专有的,有时可以从C函数返回0来表示成功,有时为-1,或者其中任何一个为失败而1为成功.即使它们被枚举,枚举也可能是模糊的.
例外也可以提供更多的信息,特别是说明"错误的东西,这里是什么,堆栈跟踪和上下文的一些支持信息"
话虽这么说,一个列举良好的返回代码对于一组已知的结果非常有用,这是一个简单的"函数结果,它只是以这种方式运行"