您更喜欢哪种和为什么使用例外或返回码?


92

我的问题是,大多数开发人员更喜欢错误处理,异常或错误返回码。请特定于语言(或语言家族),以及为什么您偏爱另一种语言。

我出于好奇而问这个。就我个人而言,我更喜欢错误返回码,因为它们的爆炸性较小,并且如果不想这样做,也不必强迫用户代码支付异常性能损失。

更新:感谢您的所有答案!我必须说,尽管我不喜欢带有异常的代码流的不可预测性。关于返回码(及其兄长句柄)的答案确实给代码增加了很多噪音。


1
来自软件世界的鸡肉或鸡蛋问题……永远存在争议。:)
Gishu

抱歉,希望大家有各种各样的意见可以帮助人们(包括我自己)适当地选择。
罗伯特·古尔德

Answers:


107

对于某些语言(例如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
}

最后,您的代码是标识指令的集合(我在生产代码中看到了这种代码)。

这段代码可以翻译成:

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,它可以返回布尔值“ false”(表示失败)
  • doSomethingElseAgain,它可以返回一个Error对象(带有__LINE __,__ FILE__和一半堆栈变量。
  • doTryToDoSomethingWithAllThisMess其中,使用上面的函数,并返回类型为错误的错误代码...

如果doTryToDoSomethingWithAllThisMess的返回函数之一失败,返回的类型是什么?

返回码不是通用解决方案

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

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

以上几点的推论。如果我想写怎么办:

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

我不能,因为返回值已经被使用了(有时不能更改)。因此,返回值成为第一个参数,作为参考发送。

键入异常

您可以为每种异常发送不同的类。资源异常(即内存不足)应该比较轻,但是其他任何东西都可以根据需要进行繁重的处理(我喜欢Java Exception,它为我提供了整个堆栈)。

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

切勿在没有重新抛出的情况下使用catch(...)

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

例外是... NUKE

例外的问题是过度使用它们会产生充满try / catches的代码。但是问题出在别处:谁使用STL容器尝试/捕获他/她的代码?这些容器仍然可以发送异常。

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

异常是...同步

在它们使您的线程屈膝或在Windows消息循环内传播之前,请务必抓住它们。

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

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

因此,唯一的问题是“什么不应该发生?”

这取决于您的职能合同。如果该函数接受一个指针,但指定该指针必须为非NULL,则可以在用户发送NULL指针时引发异常(问题是,在C ++中,函数作者未使用引用代替)指针,但是...)

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

有时,您的问题是您不希望出错。使用异常或错误返回码很酷,但是...您想知道这一点。

在我的工作中,我们使用一种“声明”。无论调试/发布编译选项如何,它都将取决于配置文件的值:

  • 记录错误
  • 打开带有“嘿,您有问题”的消息框
  • 打开带有“嘿,您有问题,您要调试”的消息框

在开发和测试中,这都使用户能够准确地查明问题的时间,而不是事后(当某些代码在乎返回值或在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.
}

(我有类似的宏,它们仅在调试时才处于活动状态)。

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

结论

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

当您使用异常进行编码时,您知道您的代码可能会失败,并且通常会在代码中选定的战略位置放上反扑之门。但是通常,您的代码更多地是关于“它必须做什么”,而不是“我担心会发生什么”。

但是,当您编写代码时,必须使用最好的工具,有时,这是“永远不要隐藏错误,并尽快显示它”。我上面所说的宏遵循这种哲学。


3
是的,但是关于您的第一个示例,可以很容易地写成: if( !doSomething() ) { puts( "ERROR - doSomething failed" ) ; return ; // or react to failure of doSomething } if( !doSomethingElse() ) { // react to failure of doSomethingElse() }
bobobobo 2012年

为什么不...但是,我仍然发现doSomething(); doSomethingElse(); ...更好,因为如果需要添加if / while / etc。出于正常执行目的的语句,我不希望它们与if / while / etc混合使用。出于特殊目的而添加的语句...由于使用异常的真正规则是throw而不是catch,try / catch语句通常不是侵入性的。
paercebal 2012年

1
您的第一点说明了异常的问题所在。您的控制流程很奇怪,并且与实际问题分开了。它正在通过一系列的捕获代替某些级别的标识。我会同时使用返回代码(或带有大量信息的返回对象)来处理可能的错误和意外情况,这是意外的
彼得

2
@Peter Weber:这并不奇怪。它与实际问题分开,因为它不是正常执行流程的一部分。这是一个非凡的执行。再有,关于异常的要点是,如果发生异常错误,则经常抛出,而很少捕获(如果有的话)。因此,捕获块甚至很少出现在代码中。
paercebal

这种辩论中的例子非常简单。通常,“ doSomething()”或“ doSomethingElse()”实际上会执行某些操作,例如更改某些对象的状态。异常代码不能保证将对象返回到先前的状态,甚至不能保证在catch距离抛出很远时返回的对象更少。例如,假设doSomething被调用了两次,并在抛出之前增加了一个计数器。您如何知道在捕获应减一两次的异常时?通常,很难为任何不是玩具示例的内容编写异常安全代码(不可能吗?)。
xryl669

36

我实际上都使用。

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

异常仅用于我不期望的事情。


+1非常简单。Atleast它足够简短,因此我可以很快阅读。:-)
Ashish Gupta'5

1
异常仅用于我不期望的事情。 ”如果您不期望它们,那么为什么或如何使用它们呢?
anar khalilov 2013年

5
仅仅因为我不希望它发生,并不意味着我看不到它怎么可能。我希望我的SQL Server可以打开并响应。但是我仍然按照我的期望进行编码,以便在发生意外停机时能够正常运行。
斯蒂芬·赖顿2013年

不响应的SQL Server不会轻易归类为“已知的可能错误”吗?
tkburbidge

21

根据“框架设计指南:可重用.NET库的约定,惯用语和模式”中标题为“异常”的第7章,给出了许多理由,说明了为什么对于C#这样的OO框架必须在返回值上使用异常。

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

“异常与面向对象的语言很好地集成在一起。面向对象的语言倾向于对成员签名施加约束,这些约束不是由非OO语言中的函数施加的。例如,对于构造函数,运算符重载和属性,开发人员没有选择返回值的方法,因此,无法针对面向对象的框架标准化基于返回值的错误报告错误报告方法(例如异常)超出了方法签名的范围是唯一的选择。


10

我的偏好(在C ++和Python中)是使用异常。语言提供的功能使其成为引发,捕获和(如有必要)重新抛出异常的明确定义的过程,从而使模型易于查看和使用。从概念上讲,它比返回码干净,因为可以通过异常的名称定义特定的异常,并附带其他信息。使用返回码,您将仅限于错误值(除非要定义ReturnStatus对象或其他内容)。

除非您正在编写的代码对时间要求严格,否则与展开堆栈相关的开销并不值得担心。


2
请记住,使用异常会使程序分析更加困难。
帕维尔Hajdan

7

仅在发生意外情况时才返回异常。

从历史上看,异常的另一点是返回码本质上是专有的,有时从C函数返回0表示成功,有时返回-1,或者其中之一表示失败,而1表示成功。即使枚举,枚举也可能是模棱两可的。

异常也可以提供更多的信息,并且可以很好地明确说明“出了点问题,这是什么,堆栈跟踪以及上下文的一些支持信息”

话虽这么说,一个良好枚举的返回码对于已知的一组结果很有用,一个简单的“函数的n个结果,就这样运行”


6

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

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

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

  3. 异常,但用于意外情况。


1
对合同使用断言不是更正确吗?如果合同违约,没有什么可以挽救您的。
帕维尔Hajdan

@PawełHajdan,我认为断言默认为禁用。这和C的assert东西有同样的问题,除非您一直运行断言,否则它将不会在生产代码中发现问题。我倾向于将断言视为在开发过程中发现问题的一种方式,但适用于将断言或未断言一致的东西(例如带有常量的东西,带有变量的东西或在运行时可能发生变化的其他东西)。
paxdiablo

十二年后才能回答您的问题。我应该经营一个服务台:-)
paxdiablo

6

返回码失败“成功的坑几乎每一次”考验我。

  • 忘记检查返回码,然后在以后出现红色鲱鱼错误,这太容易了。
  • 返回代码没有任何出色的调试信息,例如调用堆栈,内部异常。
  • 返回代码不会传播,这与上面的观点一起会导致驱动过多的和交织的诊断日志记录,而不是在一个集中的位置(应用程序和线程级异常处理程序)进行记录。
  • 返回码倾向于以嵌套的“ if”块的形式驱动混乱的代码
  • 开发人员花在调试未知问题上的时间很长,否则将是显而易见的例外(成功之路),这是很昂贵的。
  • 如果C#背后的团队不打算使用异常来控制控制流,则不会键入错误信息,在catch语句上没有“何时”过滤器,并且不需要无参数的“ throw”语句。

关于性能:

  • 相对于根本不抛出异常,异常在计算上可能是相对昂贵的,但由于某种原因,它们被称为EXCEPTIONS。速度比较总是设法假定100%的异常率,这绝非不可能。即使异常的发生速度慢100倍,但如果仅在1%的时间发生异常,这到底有多重要?
  • 除非我们在讨论图形应用程序或类似应用程序的浮点运算,否则与开发人员相比,CPU周期便宜。
  • 从时间角度来看,成本也有相同的论点。相对于数据库查询或Web服务调用或文件加载,正常的应用程序时间将使异常时间相形见war。2006年的例外情况几乎不到MICROsecond
    • 我敢于在.net中工作的任何人将调试器设置为打破所有异常并仅禁用我的代码,并查看已经发生了甚至您不知道的异常。

4

我从The Pragmatic Programmer获得了一个很好的建议,即“您的程序应该能够在不使用任何例外的情况下执行其所有主要功能”。


4
您误解了。他们的意思是“如果您的程序在正常流程中引发异常,那就错了”。换句话说,“仅将例外用于例外情况”。
帕维尔Hajdan

4

不久前,我写了一篇博客文章

引发异常的性能开销在您的决策中不起作用。毕竟,如果您做得对,那就是例外


链接的博客文章更多地是关于跳跃之前的外观(检查),而不是允许(请求或返回码)比请求许可更容易。我已经回答了有关该问题的想法(提示:TOCTTOU)。但是这个问题是一个不同的问题,即在什么条件下使用语言的异常机制而不是返回具有特殊属性的值。
Damian Yerrick '16

我完全同意。在过去的九年里,我似乎学到了一两件事;)
托马斯(Thomas

4

我不喜欢返回代码,因为它们会导致以下模式在整个代码中迅速蔓延

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

不久,由4个函数调用组成的方法调用会膨胀并带有12行错误处理。.其中一些永远不会发生。如果和切换情况比比皆是。

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

如果在方法调用后有多个状态,应以不同的方式处理(并非例外),请使用错误代码或out参数。尽管Personaly我发现这种情况很少见。

在C ++ / COM世界中,我对“性能下降”的反驳颇有争议。但是在较新的语言中,我认为差别不大。在任何情况下,当发生问题时,性能问题都将归咎于落后者:)


3

对于任何不错的编译器或运行时环境,异常不会造成重大损失。它或多或少类似于跳转到异常处理程序的GOTO语句。此外,运行时环境(如JVM)捕获异常有助于更轻松地隔离和修复错误。我每天都会通过C中的段错误来获取Java中的NullPointerException。


2
异常非常昂贵。他们必须走栈才能找到潜在的异常处理程序。这种栈步并不便宜。如果构建了堆栈跟踪,它的成本甚至更高,因为必须解析整个堆栈。
德里克公园

我很惊讶编译器至少在某些时候无法确定异常的捕获位置。另外,在我看来,异常会更改代码流,这使得更容易准确地确定错误发生的位置,从而弥补了性能损失。
凯尔·克罗宁

调用栈在运行时会变得非常复杂,并且编译器通常不会进行这种分析。即使他们这样做了,您仍然必须走栈来追踪。您还必须放松堆栈,以处理finally堆栈分配对象的块和析构函数。
德里克·帕克

我确实同意,例外的调试优势通常可以弥补性能成本。
德里克·帕克 Derek Park)

1
德拉克公园,发生异常时代价很高。这就是它们不应被过度使用的原因。但是当它们不发生时,它们几乎不花任何钱。
花粉

3

我有一组简单的规则:

1)使用返回码处理您希望直接呼叫者做出反应的事情。

2)对范围更广的错误使用异常,并且可以合理地预期由调用者之上许多级别的东西来处理错误,从而使错误的意识不必遍及许多层,从而使代码更加复杂。

在Java中,我只使用过非检查型异常,检查型异常最终只是返回代码的另一种形式,根据我的经验,方法调用可能“返回”的二重性通常是障碍而不是帮助。


3

在异常和非异常情况下,我都在python中使用Exceptions。

与返回Error值相反,能够使用Exception来指示“无法执行请求”通常是很好的选择。这意味着您/总是/知道返回值是正确的类型,而不是任意的None或NotFoundSingleton之类的东西。这是一个很好的示例,说明了我更喜欢使用异常处理程序而不是返回值的条件。

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

副作用是,当运行datastore.fetch(obj_id)时,您无需检查其返回值是否为None,即可立即免费获得该错误。这与参数“您的程序应该能够执行其所有主要功能而不使用任何例外情况”相反。

这是异常在“异常情况”中有用的另一个示例,以便编写用于处理不受竞争条件约束的文件系统的代码。

# 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失败,但是对于许多严格控制的情况,这是一个“足够好”的解决方案。


第二个例子是令人误解的。推测错误的方式是错误的,因为os.path.rmdir代码旨在引发异常。返回代码流中的正确实现是“如果rmdir(...)== FAILED:pass”
2013年

3

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

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


1
COM旨在被不支持异常的语言使用。
凯文”

这是一个好点。处理脚本语言的错误代码很有意义。至少VB6通过将错误代码详细信息封装在Err对象中,可以很好地隐藏它们,这在某种程度上有助于更清晰的代码。
rpattabi's

我不同意:VB6仅记录最后一个错误。结合臭名昭著的“下一步恢复错误”,到您看到问题时,您将完全错过问题的根源。请注意,这是Win32 APII中错误处理的基础(请参阅GetLastError函数)
paercebal

2

我更喜欢将异常用于错误处理,并将返回值(或参数)作为函数的正常结果。这提供了一种简单且一致的错误处理方案,如果正确完成,则可以使代码看起来更简洁。


2

最大的区别之一是,异常会强制您处理错误,而错误返回码可能会未经检查。

错误返回码如果大量使用,也可能导致非常丑陋的代码,其中包含许多类似于以下形式的if测试:

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

就我个人而言,我更喜欢使用异常来处理调用代码应该或必须执行的错误,并且仅将错误代码用于“预期的失败”,以返回实际有效且可能的返回值。


至少在C / C ++和gcc中,您可以为函数指定一个属性,该属性将在忽略其返回值时生成警告。
帕维尔Hajdan

phjr:虽然我不同意“返回错误代码”模式,但是您的评论也许应该成为完整的答案。我觉得这很有趣。至少,它确实为我提供了有用的信息。
paercebal

2

与返回代码相比,有很多原因更喜欢使用Exceptions:

  • 通常,出于可读性考虑,人们尝试将方法中return语句的数量减至最少。这样做,异常会阻止处于不正确状态时进行一些额外的工作,从而防止潜在地损坏更多数据。
  • 通常,异常比返回值更冗长,而且更容易扩展。假定方法返回自然数,并且在发生错误时使用负数作为返回码,如果方法的范围发生变化并现在返回整数,则您将不得不修改所有方法调用,而不仅仅是进行一些调整例外。
  • 异常允许更轻松地将正常行为的错误处理分开。它们允许确保某些操作以某种方式作为原子操作执行。

2

IMO不针对错误处理提供例外。例外就是这样。您没想到的特殊事件。我说请谨慎使用。

错误代码可以,但是从方法中返回404或200不好,IMO。使用枚举(.Net)代替,这会使代码更具可读性,并易于其他开发人员使用。另外,您不必维护有关数字和描述的表格。

也; try-catch-finally模式是我书中的反模式。try-finally可能很好,try-catch也可能很好,但是try-catch-finally永远不好。try-finally通常可以用“ using”语句(IDispose模式)代替,这是更好的IMO。在实际捕获可以处理的异常的地方,Try-catch很好,或者如果您这样做,也可以:

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

因此,只要让异常继续冒泡就可以了。另一个例子是:

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

在这里,我实际上处理了异常;当update方法失败时,我在try-catch之外所做的是另一回事,但我认为我的观点已经提出。:)

为什么最后尝试赶上反模式?原因如下:

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

如果db对象已经关闭,会发生什么?引发新异常,必须对其进行处理!这个更好:

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美分!:)


如果对象具有.Close()但不具有.Dispose(),这将很奇怪
abatishchev 2009年

我仅以Close()为例。随意将其视为其他内容。如我所说;如果可以的话,应该使用using模式(doh!)。当然,这意味着该类实现了IDisposable,因此您可以调用Dispose。
noocyte

1

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

我遵循的一般规则是,如果我有一个称为的方法,doFoo()则它遵循的是,如果它不像以前那样“做foo”,则发生了异常情况,应抛出异常。


1

我担心异常的一件事是,抛出异常会破坏代码流。例如,如果您这样做

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

甚至更糟的是,如果我删除了本不应该拥有的内容,但是在进行其余的清理工作之前就被赶上了。投掷给可怜的用户恕我直言很大的负担。


这就是<code> finally </ code>语句的用途。但是,可惜,它不在C ++标准中……
Thomas

在C ++中,您应该遵循经验法则:“在construcotor中获取资源,然后在destrucotor中释放它们。对于这种特殊情况,auto_ptr将会非常有用。–
Serge

托马斯,你错了。C ++最终没有,因为它不需要它。它有RAII。Serge的解决方案是使用RAII的一种解决方案。
paercebal

罗伯特,使用Serge的解决方案,您会发现问题消失了。现在,如果您编写的try / catches比throw多,那么(通过判断您的评论)也许您的代码有问题。当然,在没有重新抛出的情况下使用catch(...)通常是不好的,因为它可以隐藏错误以更好地忽略它。
paercebal

1

我在例外与返回码参数中的一般规则:

  • 需要本地化/国际化时使用错误代码-在.NET中,您可以使用这些错误代码来引用资源文件,该文件随后将以适当的语言显示错误。否则,请使用例外
  • 仅将异常用于真正异常的错误。如果这种情况经常发生,请使用布尔值或枚举错误代码。

执行l10n / i18n时没有理由不能使用异常。异常也可以包含本地化信息。
gizmo

1

我发现返回码并不比异常丑陋。除此以外,您拥有的try{} catch() {} finally {}位置和返回代码一样if(){}。由于帖子中给出的原因,我过去常常害怕例外。您不知道是否需要清除指针,您呢。但是我认为您在返回码方面也有同样的问题。您不知道参数的状态,除非您知道有关所讨论的函数/方法的一些详细信息。

无论如何,如果可能,您必须处理该错误。您可以轻松地让异常传播到顶层,就像忽略返回代码并让程序进行段错误一样。

我很喜欢为结果返回值(枚举?),对于特殊情况返回异常的想法。


0

对于Java之类的语言,我会选择Exception,因为如果不处理异常,编译器会给出编译时错误,这会强制调用函数处理/抛出异常。

对于Python,我更感到矛盾。没有编译器,因此调用者有可能不处理导致运行时异常的函数引发的异常。如果使用返回码,则如果处理不当,则可能会发生意外的行为;如果使用异常,则可能会获得运行时异常。


0

我通常更喜欢返回码,因为它们可以让调用者确定失败是否是异常的

这种方法在Elixir语言中很典型。

# I care whether this succeeds. If it doesn't return :ok, raise an exception.
:ok = File.write(path, content)

# I don't care whether this succeeds. Don't check the return value.
File.write(path, content)

# This had better not succeed - the path should be read-only to me.
# If I get anything other than this error, raise an exception.
{:error, :erofs} = File.write(path, content)

# I want this to succeed but I can handle its failure
case File.write(path, content) do
  :ok => handle_success()
  error => handle_error(error)
end

人们提到,返回码可以使您有很多嵌套的if语句,但是可以使用更好的语法来处理。在Elixir中,该with语句使我们可以轻松地从任何失败中分离出一系列快乐路径返回值。

with {:ok, content} <- get_content(),
  :ok <- File.write(path, content) do
    IO.puts "everything worked, happy path code goes here"
else
  # Here we can use a single catch-all failure clause
  # or match every kind of failure individually
  # or match subsets of them however we like
  _some_error => IO.puts "one of those steps failed"
  _other_error => IO.puts "one of those steps failed"
end

Elixir仍然具有引发异常的功能。回到我的第一个示例,如果文件无法写入,我可以执行上述任一操作来引发异常。

# Raises a generic MatchError because the return value isn't :ok
:ok = File.write(path, content)

# Raises a File.Error with a descriptive error message - eg, saying
# that the file is read-only
File.write!(path, content)

如果我(作为调用者)知道如果写入失败要提出错误,则可以选择致电File.write!而不是File.write。或者,我可以选择不同地致电File.write和处理失败的每种可能原因。

当然,rescue如果我们愿意,总是有可能例外。但是与处理信息丰富的返回值相比,对我来说似乎很尴尬。如果我知道一个函数调用可能失败,甚至应该失败,那么它的失败也不例外。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.