哪一个,为什么你喜欢例外或返回码?

我的问题是大多数开发人员喜欢error handling,例外或错误返回代码。 请成为语言(或语言家庭)具体的,为什么你喜欢一个在另一个。

我好奇地问这个。 就个人而言,我更喜欢错误返回码,因为它们不易爆炸,如果不想强制用户代码支付exception性能处罚。

更新:感谢所有的答案! 我必须说,尽pipe我不喜欢有例外的代码stream的不可预测性。 关于返回代码(和他们的哥哥处理)的答案确实增加了噪声的代码。

对于某些语言(即C ++)资源泄漏不应该是一个原因

C ++基于RAII。

如果你的代码可能会失败,返回或抛出(也就是最普通的代码),那么你应该把你的指针包装在一个智能指针内(假设你有一个非常好的理由没有在堆栈上创build对象)。

返回码更详细

它们是冗长的,并倾向于发展成如下forms:

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 } 

这清楚地分离代码和error handling,这可能是一件好事。

返回码更易碎

如果不是来自一个编译器的一些模糊的警告(请参阅“phjr”的评论),他们可以很容易被忽略。

通过上面的例子,假设比别人忘记处理其可能的错误(这发生…)。 这个错误在“返回”时被忽略,并且可能会在稍后爆发(即一个NULL指针)。 exception情况不会发生同样的问题。

错误不会被忽略。 有时候,你希望它不会爆炸,但是…所以你必须仔细select。

返回代码有时必须翻译

假设我们有以下function:

  • doSomething,它可以返回一个名为NOT_FOUND_ERROR的int
  • doSomethingElse,它可以返回一个布尔“假”(失败)
  • doSomethingElseSagain,它可以返回一个Error对象(同时包含__LINE__,__FILE__和一半的堆栈variables。
  • doTryToDoSomethingWithAllThisMess其中,以及…使用上述function,并返回types的错误代码…

doTryToDoSomethingWithAllThisMess的返回types是什么,如果其中一个被调用的函数失败?

返回代码不是一个通用的解决scheme

操作员不能返回错误代码。 C ++构造函数也不能。

返回码意味着你不能链expression式

上述观点的必然结果。 如果我想写:

 CMyType o = add(a, multiply(b, c)) ; 

我不能,因为返回值已被使用(有时,它不能被改变)。 所以返回值成为第一个参数,作为参考发送…或不。

exception是键入

您可以为每种exception发送不同的类。 重定位exception(即内存不足)应该很轻,但是其他任何东西都可能会很重要(我喜欢Javaexception给我整个堆栈)。

然后每个渔获物可以是专门的。

不要使用catch(…)而不要重新投掷

通常,你不应该隐藏一个错误。 如果你不重新抛出,至less在文件中logging错误,打开一个消息框,不pipe…

例外是… NUKE

这个例外的问题是过度使用它们会产生充满try / catch的代码。 但是问题在其他地方:谁使用STL容器尝试/抓住他/她的代码? 但是,这些容器可以发送exception。

当然,在C ++中,不要让exception退出析构函数。

exception是…同步的

一定要抓住他们之前,他们的膝盖,或传播你的Windows消息循环内的线程。

解决scheme可以混合它们?

所以我想解决scheme是抛出什么时候不应该发生。 而且什么时候会发生,然后使用一个返回代码或一个参数来让用户对它做出反应。

所以,唯一的问题是“什么是不应该发生的事情?

这取决于你的function的合同。 如果函数接受一个指针,但指定的指针必须是非NULL,那么在用户发送一个NULL指针时抛出一个exception是可以的(问题是,在C ++中,当函数作者没有使用引用时指针,但…)

另一个解决scheme是显示错误

有时候,你的问题是你不想要错误。 使用exception或错误返回代码很酷,但是…你想知道它。

在我的工作中,我们使用了一种“断言”。 根据configuration文件的值,不pipe是debug / release编译选项:

  • logging错误
  • 打开一个“嗨,你有问题”的消息框
  • 打开一个“嗨,你有问题,你想debugging”的消息框

在开发和testing中,这使得用户能够在发现问题的时候精确地查明问题,而不是在(当某些代码关心返回值时,或者在一个catch内部时)查找问题。

遗留代码很容易添加。 例如:

 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. } 

(我有类似的macros,只有在debugging时才有效)。

请注意,在生产环境中,configuration文件不存在,所以客户端从来没有看到这个macros的结果…但是在需要的时候很容易激活它。

结论

当你使用返回码进行编码时,你正在准备自己的失败,并希望你的testing堡垒足够安全。

当你使用exception进行编码时,你知道你的代码可能会失败,并且通常会把你的代码中的战略位置放在对手的位置。 但通常情况下,你的代码更多的是关于“它必须做什么”,然后是“我担心会发生什么”。

但是,当你编码时,你必须使用最好的工具,有时候,它是“永远不要隐藏错误,尽快显示”。 我上面所说的macros观遵循这个哲学。

我实际上使用两个。

如果这是已知的,可能的错误,我使用返回码。 如果这是我知道可以发生的情况,那么会有一个代码被发回。

例外情况仅用于我不期望的事情。

根据框架devise指南:可重用.NET库的惯例,习语和模式的第7章,给出了很多理由,说明为什么使用返回值的exception对于OO框架(如C#)是必需的。

也许这是最有说服力的原因(第179页):

“exception与面向对象的语言很好地结合在一起,面向对象的语言倾向于对非OO语言中的函数强加的成员签名施加约束, 例如,在构造函数,操作符重载和属性的情况下,开发者在返回值中没有select,因此,面向对象框架的基于返回值的错误报告是不可能被标准化的, 一个错误报告方法,例如exception,这是在方法签名的带外是唯一的select。

我的首选(使用C ++和Python)是使用exception。 语言提供的工具使它成为一个定义明确的过程,既可以提升,捕获又可以(如果有必要)重新抛出exception,使模型易于查看和使用。 从概念上讲,它比返回代码更清洁,因为特定的例外可以通过它们的名字来定义,并且具有附加的信息。 使用返回码,你只能限制错误值(除非你想定义一个ReturnStatus对象或其他东西)。

除非你写的代码对时间要求很高,否则与展开堆栈相关的开销并不足以担心。

例外情况应该只在你不期待的事情发生的时候返回。

另一个例外的例外是,返回代码本质上是专有的,有时可以从C函数返回0来指示成功,有时为-1,或者其中任何一个为失败,1为成功。 即使列举,列举也可能不明确。

例外情况还可以提供更多的信息,特别是明确地说明“出错了,这是什么,堆栈跟踪和上下文的一些支持信息”

这就是说,一个好的列举的返回代码对于一组已知的结果可能是有用的,一个简单的“函数结果”,它就是这样运行的“

我前一段时间写了一篇关于这个的博客文章 。

抛出exception的性能开销不应该影响你的决定。 如果你做得对,毕竟例外是例外

我不喜欢返回代码,因为它们会导致整个代码中出现以下模式

 CRetType obReturn = CODE_SUCCESS; obReturn = CallMyFunctionWhichReturnsCodes(); if (obReturn == CODE_BLOW_UP) { // bail out goto FunctionExit; } 

很快,一个由4个函数调用组成的方法调用就有了12行error handling,其中一些永远不会发生。 如果和切换案件比比皆是。

如果您使用它们,例外情况会更加清晰……发出exception事件的信号,之后执行path将无法继续。 它们通常比错误代码更具描述性和信息性。

如果在应该以不同方式处理方法调用(并且不是特例)之后有多个状态,请使用错误代码或out参数。 虽然Personaly我发现这是罕见的..

我已经find了一些关于“性能损失”的反驳。更多的是在C ++ / COM世界,但是在新语言中,我认为差异不是那么大。 无论如何,当一些事情发生时,性能问题就被放到了后退的位置:)

在Java中,我使用(按以下顺序):

  1. 按合同devise(确保在尝试可能失败的任何事情之前先满足先决条件)。 这捕获了大部分的东西,我为此返回一个错误代码。

  2. 在处理工作时返回错误代码(如果需要,则执行回滚)。

  3. 例外,但这些用于意想不到的事情。

使用任何体面的编译器或运行时环境exception都不会招致重大的惩罚。 它或多或less像跳转到exception处理程序的GOTO语句。 此外,运行时环境(如JVM)捕获的exception有助于更容易地隔离和修复错误。 我会在任何一天在C语言的segfault中使用Java中的NullPointerException。

我在例外情况和非例外情况下在python中使用exception。

能够使用Exception来指示“请求不能被执行”,而不是返回一个Error值。 这意味着你/总是/知道返回值是正确的types,而不是任意None或NotFoundSingleton或什么。 这里是我喜欢使用exception处理程序而不是返回值的条件的一个很好的例子。

 try: dataobj = datastore.fetch(obj_id) except LookupError: # could not find object, create it. dataobj = datastore.create(....) 

副作用是当运行一个datastore.fetch(obj_id)时,你永远不必检查它的返回值是否为None,你立即得到这个错误是免费的。 这与“你的程序应该能够执行其所有主要function而不使用exception”的观点相反。

这里是另外一个例子,在这个例子中,exception是“exception”有用的,以便编写代码来处理不受竞争条件影响的文件系统。

 # wrong way: if os.path.exists(directory_to_remove): # race condition is here. os.path.rmdir(directory_to_remove) # right way: try: os.path.rmdir(directory_to_remove) except OSError: # directory didn't exist, good. pass 

一个系统调用,而不是两个,没有竞争条件。 这是一个糟糕的例子,因为很明显,在更多的情况下,这个操作会失败,OSError比目录不存在,但是对于许多严格控制的情况来说,这是一个“足够好”的解决scheme。

我相信返回代码会增加代码噪声。 例如,我总是讨厌由于返回码造成的COM / ATL代码的外观。 必须对每行代码进行HRESULT检查。 我认为错误返回代码是COM架构师做出的错误决定之一。 这使得对代码进行逻辑分组变得困难,因此代码审查变得困难。

当每一行显式检查返回码时,我不确定性能比较。

exception不适用于error handling,IMO。 例外就是这样; 你没有想到的特殊事件。 谨慎使用我说。

错误代码可以正常,但是从方法返回404或200是不好的,IMO。 使用枚举(.Net),这使得代码更易读,更易于其他开发人员使用。 你也不必在数字和描述上维护一个表格。

也; try-catch-finally模式在我的书中是一个反模式。 试试最后可以很好,试试抓也可以,但试试看,最后永远不会好。 try-finally可以经常被replace为“使用”语句(IDispose模式),这是更好的IMO。 而尝试赶上,你实际上捕捉到一个exception,你能够处理好,或者如果你这样做:

 try{ db.UpdateAll(somevalue); } catch (Exception ex) { logger.Exception(ex, "UpdateAll method failed"); throw; } 

所以只要你让exception继续泡沫就可以了。 另一个例子是这样的:

 try{ dbHasBeenUpdated = db.UpdateAll(somevalue); // true/false } catch (ConnectionException ex) { logger.Exception(ex, "Connection failed"); dbHasBeenUpdated = false; } 

在这里,我实际上处理的例外; 当更新方法失败时,我在try-catch之外做的是另一回事,但是我想我的观点已经被提出。 🙂

那么为什么最后一个反模式呢? 原因如下:

 try{ db.UpdateAll(somevalue); } catch (Exception ex) { logger.Exception(ex, "UpdateAll method failed"); throw; } finally { db.Close(); } 

如果db对象已经closures会发生什么? 抛出一个新的exception,它必须被处理! 这个更好:

 try{ using(IDatabase db = DatabaseFactory.CreateDatabase()) { db.UpdateAll(somevalue); } } catch (Exception ex) { logger.Exception(ex, "UpdateAll method failed"); throw; } 

或者,如果db对象没有实现IDisposable,可以这样做:

 try{ try { IDatabase db = DatabaseFactory.CreateDatabase(); db.UpdateAll(somevalue); } finally{ db.Close(); } } catch (DatabaseAlreadyClosedException dbClosedEx) { logger.Exception(dbClosedEx, "Database connection was closed already."); } catch (Exception ex) { logger.Exception(ex, "UpdateAll method failed"); throw; } 

这是我的2美分呢! 🙂

我只使用exception,没有返回码。 我在这里谈论Java。

我遵循的一般规则是如果我有一个叫做doFoo()的方法,那么如果它不是“做foo”,那么发生了一些特殊的事情,并抛出exception。

我从“实用程序员”得到的一个很好的build议就是“你的程序应该能够执行其所有主要function而不需要使用例外”。

我更喜欢使用exception来处理错误,并将返回值(或参数)作为函数的正常结果。 这提供了一个简单和一致的error handlingscheme,如果做得正确,它使得更清洁的代码。

最大的区别之一是exception会迫使你处理错误,而错误返回码可以不加select。

错误返回代码,如果使用过多,也可能导致非常难看的代码,如果testing类似于这种forms很多:

 if(function(call) != ERROR_CODE) { do_right_thing(); } else { handle_error(); } 

就个人而言,我更喜欢使用exception来处理调用代码应该或必须执行的错误,并且仅在错误代码中使用错误代码来表示返回某些东西实际上是有效的和可能的。

有很多原因喜欢例外返回代码:

  • 通常,为了可读性,人们尽量减less方法中返回语句的数量。 这样做,exception会阻止在incoorect状态下做一些额外的工作,从而防止潜在地损害更多的数据。
  • exception通常比返回值更加冗长。 假设一个方法返回自然数,并且当发生错误时使用负数作为返回码,如果你方法的范围改变了,现在返回整数,你将不得不修改所有的方法调用,而不是只调整一点点例外。
  • exception允许更多的easilly分开正常行为的error handling。 它们允许确保某些操作以某种方式作为primefaces操作执行。

我担心exception的一件事是抛出一个exception将会破坏代码stream。 例如,如果你这样做

 void foo() { MyPointer* p = NULL; try{ p = new PointedStuff(); //I'm a module user and I'm doing stuff that might throw or not } catch(...) { //should I delete the pointer? } } 

甚至更糟的是,如果我删除了一些我不应该拥有的东西,但是在我完成其余的清理工作之前就被抛弃了。 投掷对可怜的用户恕我直言,很多的重量。

我在exception与返回代码参数的一般规则:

  • 在需要本地化/国际化时使用错误代码 – 在.NET中,您可以使用这些错误代码来引用资源文件,然后使用相应的语言显示错误。 否则,使用例外
  • 只对非常例外的错误使用例外。 如果这是经常发生的事情,可以使用布尔值或枚举错误代码。

我有一套简单的规则:

1)使用返回代码您希望您的直接呼叫者作出反应的东西。

2)对范围更广泛的错误使用exception,并且可以合理地预期由调用者之上的许多层次来处理,以便错误的意识不必渗透到许多层,使代码更复杂。

在Java中,我只使用了未经检查的exception,检查exception最终只是另一种forms的返回代码,根据我的经验,方法调用可能“返回”的二元性通常更多地是一种障碍而不是帮助。

我不觉得返回代码比例外更不难看。 除了例外,你可以用try{} catch() {} finally {}if(){}返回码一样。 出于这个职位的原因,我曾经担心过例外情况。 你不知道指针是否需要清除,你有什么。 但是我认为在返回代码方面你有同样的问题。 你不知道参数的状态,除非你知道一些有关函数/方法的细节。

无论如何,如果可能的话,你必须处理错误。 您可以轻松地让exception传播到顶层,因为忽略返回代码并让程序段错误。

我喜欢为结果返回一个值(枚举?)的想法,以及一个例外情况。

回报代码几乎每次都会让我失败。

  • 忘记检查退货代码,然后在代码中出现红鲱鱼错误是非常容易的。
  • 返回码没有像调用堆栈,内部exception那样的很好的debugging信息。
  • 返回代码不会传播,与上面的点一起,会导致过多的交错式诊断日志而不是一个集中的地方
  • 返回代码倾向于以嵌套的“if”块的forms驱动混乱的代码
  • 开发人员花时间debugging不必要的(否则被成功的陷阱所捕获)或难以追查的代价是昂贵的。

关于performance:

  • 例外可能是计算成本昂贵相对于不投掷,但他们被称为EXCEPTIONS有一个原因。 速度比较总是假设100%的例外率,而这种情况决不应该是这样。 即使有一个例外是100倍慢,如果它只发生1%的时间,这个真的有多重要?
  • 除非我们正在谈论graphics应用的浮点运算,否则与开发者时间相比,CPU周期是便宜的。
  • 从时间的angular度来看,成本也是一样的。 相对于数据库查询或Web服务调用或文件加载,正常的应用程序时间将会使exception时间变得更短。 2006年的例外情况接近亚微米级
    • 我敢打赌任何在.net中工作的人都可以设置你的debugging器来打破所有的exception,并禁用我的代码,看看有多lessexception已经发生,你甚至不知道。