为什么,您更喜欢例外或返回代码?

Rob*_*uld 80 language-agnostic exception

我的问题是大多数开发人员更喜欢错误处理,异常或错误返回代码.请具体说明语言(或语言家族)以及为什么您喜欢其中一种语言.

我出于好奇而问这个问题.我个人更喜欢错误返回代码,因为它们不那么具有爆炸性,并且如果不想要,也不会强制用户代码支付异常性能损失.

更新:感谢所有答案!我必须说,虽然我不喜欢代码流与异常的不可预测性.关于返回代码(以及他们的哥哥句柄)的答案会给代码添加大量的噪音.

pae*_*bal 98

对于某些语言(即C++),资源泄漏不应成为原因

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
}
Run Code Online (Sandbox Code Playgroud)

最后,您的代码是一组精心指令(我在生产代码中看到了这种代码).

这段代码可以很好地翻译成:

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
}
Run Code Online (Sandbox Code Playgroud)

这干净分开代码和错误处理,这可以是一个很好的事情.

退货代码更脆弱

如果不是来自一个编译器的一些模糊警告(参见"phjr"的评论),它们很容易被忽略.

假设有人忘记处理可能的错误(这种情况发生......)."返回"时忽略该错误,稍后可能会爆炸(即NULL指针).异常不会发生同样的问题.

错误不会被忽略.有时,你希望它不会爆炸,但是......所以你必须谨慎选择.

有时必须翻译返回代码

假设我们有以下功能:

  • doSomething,它可以返回一个名为NOT_FOUND_ERROR的int
  • doSomethingElse,可以返回bool"false"(失败)
  • doSomethingElseSagain,它可以返回一个Error对象(包含__LINE __,__ FILE__和一半堆栈变量.
  • doTryToDoSomethingWithAllThisMess,嗯......使用上面的函数,并返回类型错误代码...

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

返回代码不是通用的解决方案

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

返回代码意味着您不能链接表达式

上述观点的必然结果.如果我想写怎么办:

CMyType o = add(a, multiply(b, c)) ;
Run Code Online (Sandbox Code Playgroud)

我不能,因为已经使用了返回值(有时候,它无法更改).所以返回值成为第一个参数,作为参考发送......或者不是.

键入例外

您可以为每种异常发送不同的类.Ressources异常(即内存不足)应该很轻,但其他任何东西都可能是必要的(我喜欢Java Exception给我整个堆栈).

然后每个捕获物都可以专门化.

不要在没有重新投掷的情况下使用catch(...)

通常,您不应该隐藏错误.如果你不重新投掷,至少,将错误记录在一个文件中,打开一个消息框,无论如何......

例外是...... NUKE

异常的问题是过度使用它们会产生充满try/catches的代码.但问题出在其他地方:谁使用STL容器尝试/捕获他/她的代码?但是,这些容器可以发送异常.

当然,在C++中,不要让异常退出析构函数.

例外是...同步

一定要抓住它们,然后再将它们放在膝盖上,或者在Windows消息循环中传播.

解决方案可能是混合它们?

所以我想解决的办法就是扔东西的时候应该不会发生.当某些事情发生时,然后使用返回代码或参数来使用户能够对其做出反应.

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

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

另一种解决方案是显示错误

有时,您的问题是您不想要错误.使用异常或错误返回代码很酷,但是......你想知道它.

在我的工作中,我们使用了一种"断言".无论调试/发布编译选项如何,它都将取决于配置文件的值:

  • 记录错误
  • 用"嘿,你有问题"打开一个消息框
  • 用"嘿,你有问题,你想调试"打开一个消息框

在开发和测试中,这使用户能够在检测到问题时精确查明问题,而不是在(某些代码关心返回值或陷阱内)之后.

可以轻松添加到旧代码中.例如:

void doSomething(CMyObject * p, int iRandomData)
{
   // etc.
}
Run Code Online (Sandbox Code Playgroud)

引出一种类似于以下的代码:

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.
}
Run Code Online (Sandbox Code Playgroud)

(我有类似的宏只在调试时有效).

请注意,在生产时,配置文件不存在,因此客户端永远不会看到此宏的结果......但是在需要时很容易激活它.

结论

当您使用返回代码进行编码时,您正在为失败做好准备,并希望您的测试堡垒足够安全.

当您使用异常进行编码时,您知道您的代码可能会失败,并且通常会将反火箭捕获到代码中选定的战略位置.但通常,你的代码更多的是"它必须做什么"然后"我担心会发生什么".

但是当您编写代码时,您必须使用最好的工具,有时候,它是"永远不会隐藏错误,并尽快显示".我上面谈到的宏观遵循这一理念.

  • 是的,但是关于你的第一个例子,可以很容易地写成:`if( !doSomething() ) { puts( "ERROR - doSomething failed" ) ; 返回 ; // 或对 doSomething 的失败做出反应 } if( !doSomethingElse() ) { // 对 doSomethingElse() 的失败做出反应 }` (4认同)
  • 你的第一点显示了异常的问题是什么。您的控制流变得奇怪并且与实际问题分离。它正在用一连串的渔获量代替某些级别的识别。我会同时使用返回代码(或返回具有大量信息的对象)来处理可能的错误和异常的事情。 (2认同)
  • @Peter Weber:这并不奇怪.它与实际问题分开,因为它不是**正常**执行流程的一部分.这是**特殊**执行.然后,关于异常的观点是,如果**异常**错误,**经常**,**很少**,如果有的话.所以catch块甚至很少出现在代码中. (2认同)

Ste*_*ton 31

我实际上都用了.

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

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

  • 仅仅因为我不期待它发生,并不意味着我无法看到它是如何发生的.我希望我的SQL Server能够打开并响应.但我仍然编码我的期望,以便在发生意外停机时我可以优雅地失败. (5认同)
  • “_异常仅用于我不期望的事情。_”如果您不期望它们,那么为什么要使用它们或如何使用它们? (2认同)
  • 没有响应的 SQL Server 是否可以轻松地归类为“已知的、可能的错误”? (2认同)

小智 20

根据框架设计指南中的第7章"异常" :可重用.NET库的约定,惯用语和模式,给出了为什么对于诸如C#的OO框架使用异常而不是返回值的原因.

也许这是最令人信服的理由(第179页):

"例外用面向对象的语言很好地集成.面向对象的语言倾向于强加不是由在非面向对象的语言中的函数施加构件签名的约束.例如,在构造函数,运算符重载和属性的情况下,显影剂在返回值别无选择.出于这个原因,它是不可能对面向对象的框架为基础的返回值的错误报告规范.错误报告方法,如例外,这是该方法的签名的带是唯一的选择. "


Jas*_*dge 10

我的偏好(在C++和Python中)是使用异常.语言提供的工具使其成为一个定义良好的过程,既可以提升,捕获和(如有必要)重新抛出异常,使模型易于查看和使用.从概念上讲,它比返回代码更清晰,因为特定的例外可以通过其名称来定义,并附带其他信息.使用返回代码,您仅限于错误值(除非您要定义ReturnStatus对象或其他内容).

除非您编写的代码对时间要求严格,否则与展开堆栈相关的开销并不足以让人担心.

  • 请记住,使用异常会使程序分析变得更难. (2认同)

joh*_*hnc 7

只有在您不期望发生的事情发生时,才应返回例外情况.

从历史上看,另一个例外是返回代码本质上是专有的,有时可以从C函数返回0来表示成功,有时为-1,或者其中任何一个为失败而1为成功.即使它们被枚举,枚举也可能是模糊的.

例外也可以提供更多的信息,特别是说明"错误的东西,这里是什么,堆栈跟踪和上下文的一些支持信息"

话虽这么说,一个列举良好的返回代码对于一组已知的结果非常有用,这是一个简单的"函数结果,它只是以这种方式运行"


pax*_*blo 7

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

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

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

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

  • 十二年的时间来回答你的问题。我应该设立一个服务台:-) (2认同)

b_l*_*itt 7

使用异常! 对我来说,答案非常明确。当上下文规定(即 cpu 密集型或公共 api)尝试或测试者-执行者模式时,我将另外向异常抛出版本提供这些方法。我认为避免异常的一揽子规则是错误的、不受支持的,并且可能比他们声称可以防止的任何性能问题导致更多的错误成本。

不,微软没有说不使用异常(常见的误解)。

它说,如果您正在设计 API,请提供方法来帮助该 API 的用户在需要时避免抛出异常(Try and Tester-Doer patterns)

? 如果可能,不要在正常的控制流中使用异常。

除了系统故障和具有潜在竞争条件的操作外,框架设计者应该设计 API,以便用户可以编写不会引发异常的代码。例如,您可以提供一种在调用成员之前检查前提条件的方法,以便用户可以编写不会引发异常的代码。

这里推断的是,非测试者/非尝试实现应该在失败时抛出异常,然后用户可以将其更改为您的测试者之一或尝试方法以提高性能。成功的坑是为了安全而维护的,用户选择更危险但更高效的方法。

微软确实说不要使用返回代码两次,这里

? 不要返回错误代码。

异常是框架中报告错误的主要方式。

?? 务必通过抛出异常来报告执行失败。

这里

? 不要使用错误代码,因为担心异常可能会对性能产生负面影响。

为了提高性能,可以使用 Tester-Doer 模式或 Try-Parse 模式,在接下来的两节中进行了描述。

如果您不使用异常,您可能违反了从非测试人员/非尝试实现返回返回代码或布尔值的其他规则。同样,TryParse 不会取代 Parse。它是在 Parse 之外提供的

主要原因:返回代码几乎每次都无法通过“成功坑”测试。

  • 忘记检查返回码然后稍后出现红鲱鱼错误太容易了。
    • var 成功 = Save()? 有多少性能值得人们忘记这里的 if 检查?
    • var 成功 = TrySave()? 更好,但我们会滥用 TryX 模式的所有内容吗?你还提供了 Save 方法吗?
  • 返回代码没有任何关于它们的重要调试信息,如调用堆栈、内部异常。
  • 返回代码不会传播,这与上面的一点一起,往往会驱动过度和交织的诊断日志记录,而不是在一个集中的地方(应用程序和线程级异常处理程序)进行日志记录。
  • 返回代码倾向于以嵌套的“if”块的形式驱动混乱的代码
  • 开发人员花在调试一个未知问题上的时间很昂贵,否则该问题将是一个明显的例外(成功之坑)。
  • 如果 C# 背后的团队不打算使用异常来管理控制流,则不会键入异常,在 catch 语句上将没有“when”过滤器,并且不需要无参数的“throw”语句.

关于性能:

  • 相对于根本不抛出异常,异常可能在计算上是昂贵的,但它们被称为异常是有原因的。速度比较总是设法假设 100% 的异常率,这不应该是这种情况。即使异常慢了 100 倍,如果它只发生 1% 的时间,这有多大意义?

  • 语境就是一切。例如,与假设成功进入并捕获罕见的冲突相比,避免唯一密钥冲突的测试者执行者或尝试选项可能会浪费更多的时间和资源(在很少发生冲突时检查是否存在)。

  • 除非我们谈论图形应用程序的浮点运算或类似的东西,否则与开发人员时间相比,CPU 周期是便宜的。

  • 从时间角度来看,成本具有相同的论点。相对于数据库查询或 Web 服务调用或文件加载,正常的应用程序时间将使异常时间相形见绌。2006 年的异常几乎是亚微秒

  • 我敢于在 .net 中工作的任何人将您的调试器设置为中断所有异常并仅禁用我的代码,并查看已经发生了多少您甚至不知道的异常。

  • Jon Skeet 说“[例外] 不够慢,因此在正常使用中避免使用它们是值得的”。链接的回复还包含 Jon 关于该主题的两篇文章。他的概括性主题是异常很好,如果您将它们视为性能问题,则可能存在更大的设计问题。


Gis*_*shu 5

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

CRetType obReturn = CODE_SUCCESS;
obReturn = CallMyFunctionWhichReturnsCodes();
if (obReturn == CODE_BLOW_UP)
{
  // bail out
  goto FunctionExit;
}
Run Code Online (Sandbox Code Playgroud)

很快,一个由 4 个函数调用组成的方法调用膨胀了 12 行错误处理。其中一些永远不会发生。If 和 switch 案例比比皆是。

如果您使用得当,异常会更清晰……发出异常事件信号……之后执行路径将无法继续。它们通常比错误代码更具描述性和信息性。

如果在方法调用后有多个状态应该以不同的方式处理(并且不是例外情况),请使用错误代码或输出参数。虽然我个人发现这种情况很少见..

我已经找到了一些关于“性能损失”的反驳……在 C++/COM 世界中更多,但在较新的语言中,我认为差异并不大。在任何情况下,当某些事情发生时,性能问题都被归为次要问题:)