Which, and why, do you prefer Exceptions or Return codes?我的问题是大多数开发人员更喜欢错误处理,异常或错误返回代码。 请具体说明语言(或语言家族)以及您喜欢哪一种。 我出于好奇而问这个问题。 我个人更喜欢错误返回代码,因为它们不那么具有爆炸性,并且如果不想要,也不会强制用户代码支付异常性能损失。 更新:感谢所有答案! 我必须说,虽然我不喜欢代码流与异常的不可预测性。 关于返回代码(以及他们的哥哥句柄)的答案会给代码添加大量的噪音。 对于某些语言(即C ++),资源泄漏不应成为原因 C ++基于RAII。 如果您的代码可能会失败,返回或抛出(也就是大多数普通代码),那么您应该将指针包装在智能指针内(假设您有充分的理由不在堆栈上创建对象)。 返回代码更详细 它们很冗长,并且倾向于发展成类似的东西:
最后,您的代码是一组精心指令(我在生产代码中看到了这种代码)。 这段代码可以很好地翻译成:
哪个干净地分离代码和错误处理,这可能是一件好事。 退货代码更脆弱 如果不是来自一个编译器的一些模糊警告(参见"phjr"的评论),它们很容易被忽略。 假设有人忘记处理可能的错误(这种情况发生......)。"返回"时忽略该错误,并且稍后可能会爆炸(即NULL指针)。异常不会发生同样的问题。 错误不会被忽略。有时,你希望它不会爆炸,但是......所以你必须谨慎选择。 有时必须翻译返回代码 假设我们有以下功能: 如果其中一个被调用的函数失败,doTryToDoSomethingWithAllThisMess的返回类型是什么? 返回代码不是通用的解决方案 操作员无法返回错误代码。 C ++构造函数也不能。 返回代码表示您无法链接表达式 上述观点的必然结果。如果我想写怎么办:
我不能,因为已经使用了返回值(有时候,它无法更改)。所以返回值成为第一个参数,作为参考发送......或者不是。 键入例外 您可以为每种异常发送不同的类。 Ressources异常(即内存不足)应该很轻,但其他任何东西都可能是必要的(我喜欢Java异常给我整个堆栈)。 然后每个捕获物都可以专门化。 不要在没有重新投掷的情况下使用catch(...) 通常,您不应该隐藏错误。如果你不重新投掷,至少,将错误记录在一个文件中,打开一个消息框,无论如何...... 例外是...... NUKE 异常的问题是过度使用它们会产生充满try / catches的代码。但问题出在其他地方:谁使用STL容器尝试/捕获他/她的代码?但是,这些容器可以发送异常。 当然,在C ++中,不要让异常退出析构函数。 例外是...同步 一定要抓住它们,然后再将它们放在膝盖上,或者在Windows消息循环中传播。 解决方案可能是混合它们? 所以我想解决方案是在不应该发生的事情时抛出。当某些事情发生时,然后使用返回代码或参数来使用户能够对其做出反应。 所以,唯一的问题是"什么是不应该发生的事情?" 这取决于你的功能合同。如果函数接受指针,但是指定指针必须是非NULL,那么当用户发送NULL指针时可以抛出异常(问题是,在C ++中,函数作者没有使用引用时指针,但......) 另一种解决方案是显示错误 有时,您的问题是您不想要错误。使用异常或错误返回代码很酷,但是......你想知道它。 在我的工作中,我们使用了一种"断言"。无论调试/发布编译选项如何,它都将取决于配置文件的值: 在开发和测试中,这使用户能够在检测到问题时精确查明问题,而不是在(某些代码关心返回值或陷阱内)之后。 可以轻松添加到旧代码中。例如:
引出一种类似于以下的代码:
(我有类似的宏只在调试时有效)。 请注意,在生产时,配置文件不存在,因此客户端永远不会看到此宏的结果......但是在需要时很容易激活它。 结论 当您使用返回代码进行编码时,您正在为失败做好准备,并希望您的测试堡垒足够安全。 当您使用异常进行编码时,您知道您的代码可能会失败,并且通常会将反火箭捕获到代码中选定的战略位置。但通常,你的代码更多的是"它必须做什么"然后"我担心会发生什么"。 但是当您编写代码时,您必须使用最好的工具,有时候,它是"永远不会隐藏错误,并尽快显示"。我上面谈到的宏观遵循这一理念。 好。 我实际上都用了。 如果它是已知的,可能的错误,我使用返回码。如果这是我知道可能会发生的情况,那么会有一个代码被发回。 例外仅用于我不期望的事情。 根据框架设计指南中的第7章"例外":可重用.NET库的约定,惯用语和模式,给出了为什么对于诸如C#的OO框架使用返回值的异常的原因。 也许这是最令人信服的理由(第179页): "异常与面向对象语言很好地集成。面向对象语言倾向于对非OO语言中的函数强加的成员签名施加约束。例如,在构造函数,运算符重载和属性的情况下,开发人员在返回值中没有选择。因此,无法对面向对象框架的基于返回值的错误报告进行标准化。一种错误报告方法,例如异常,它是方法签名的带外是唯一的选择。" 我的偏好(在C ++和Python中)是使用异常。语言提供的工具使其成为一个定义良好的过程,既可以提升,捕获和(如有必要)重新抛出异常,使模型易于查看和使用。从概念上讲,它比返回代码更清晰,因为特定的例外可以通过其名称来定义,并附带其他信息。使用返回代码,您仅限于错误值(除非您要定义ReturnStatus对象或其他内容)。 除非您编写的代码对时间要求严格,否则与展开堆栈相关的开销并不足以让人担心。 只有在您不期望发生的事情发生时,才能返回例外情况。 从历史上看,另一个例外是返回代码本质上是专有的,有时可以从C函数返回0来表示成功,有时为-1,或者其中任何一个为失败而1为成功。即使它们被枚举,枚举也可能是模糊的。 例外也可以提供更多的信息,特别是说明"错误的东西,这里是什么,堆栈跟踪和上下文的一些支持信息" 话虽这么说,一个列举良好的返回代码对于一组已知的结果非常有用,这是一个简单的"函数结果,它只是以这种方式运行" 我刚才写了一篇关于这个的博客文章。 抛出异常的性能开销不应该在您的决策中起任何作用。毕竟,如果你做得对,一个例外也是例外。 回复代码几乎每次都没有通过"成功之坑"测试。
关于表现:
我不喜欢返回代码,因为它们会导致以下模式在整个代码中出现
很快,一个由4个函数调用组成的方法调用膨胀,有12行错误处理。其中一些将永远不会发生。如果和切换案件比比皆是。 如果您使用它们,那么例外就更清晰......发出异常事件信号......之后执行路径无法继续。它们通常比错误代码更具描述性和信息性。 如果在方法调用之后有多个状态应该以不同方式处理(并且不是例外情况),请使用错误代码或输出参数。尽管Personaly我发现这很罕见.. 我已经在关于'性能惩罚'的反驳中找到了一点...更多的是在C ++ / COM世界中,但在较新的语言中,我认为差别并不大。在任何情况下,当事情爆发时,性能问题将降级为后备者:) 在Java中,我使用(按以下顺序): 按合同设计(在尝试可能失败的任何事情之前确保满足先决条件)。这捕获了大多数东西,我为此返回错误代码。 处理工作时返回错误代码(如果需要,执行回滚)。 例外,但这些只用于意外事情。 IMO不支持错误处理。例外就是这样;你没想到的特殊事件。请谨慎使用我说。 错误代码可以正常,但从方法返回404或200是不好的,IMO。使用枚举(.Net)代替,使代码更易读,更易于用于其他开发人员。此外,您不必在数字和描述上维护表格。 也; try-catch-finally模式在我的书中是一种反模式。尝试 - 终于可以是好的,尝试捕获也可以是好的,但尝试捕获 - 终于永远不会好。 try-finally通常可以被"using"语句(IDispose模式)替换,这是更好的IMO。而实际捕获你能够处理异常的Try-catch是好的,或者如果你这样做:
所以只要你让异常继续泡沫就可以了。另一个例子是:
在这里,我实际上处理了异常;当更新方法失败时我在try-catch之外做的是另一个故事,但我认为我的观点已经完成。 :) 为什么然后尝试捕捉 - 终于反模式?原因如下:
如果db对象已经关闭会发生什么?抛出一个新的异常并且必须处理它!这个更好:
或者,如果db对象未实现IDisposable,请执行以下操作:
不过,这是我的2美分! :) 我相信返回代码会增加代码噪音。例如,由于返回代码,我总是讨厌COM / ATL代码的外观。必须对每行代码进行HRESULT检查。我认为错误返回代码是COM架构师做出的错误决定之一。这使得难以对代码进行逻辑分组,因此代码审查变得困难。 当每行显式检查返回码时,我不确定性能比较。 我在Exceptional和非Exceptional情况下都在python中使用Exceptions。 能够使用Exception来指示"无法执行请求",而不是返回Error值,这通常很好。这意味着你/总是/知道返回值是正确的类型,而不是任意的None或NotFoundSingleton等。这是一个很好的例子,我更喜欢使用异常处理程序而不是返回值的条件。
副作用是当运行datastore.fetch(obj_id)时,您永远不必检查其返回值是否为None,您可以立即免费获得该错误。这与论点相反,"你的程序应该能够执行所有主要功能而不使用异常"。 下面是另一个示例,其中异常"异常"有用,以便编写用于处理不受竞争条件影响的文件系统的代码。
一个系统调用而不是两个,没有竞争条件。这是一个很糟糕的例子,因为很明显,这会因为OSError在更多情况下失败而不是目录不存在,但对于许多严格控制的情况来说,这是一个"足够好"的解决方案。 对于任何体面的编译器或运行时环境,异常都不会产生重大损失。它或多或少像跳转到异常处理程序的GOTO语句。此外,运行时环境(如JVM)捕获的异常有助于更容易地隔离和修复错误。我将在任何一天用C语言中的段错误在Java中使用NullPointerException。 我从实用程序员那里得到的一条很棒的建议就是"你的程序应该能够执行所有主要功能而不使用异常"。 我更喜欢使用异常进行错误处理并返回值(或参数)作为函数的正常结果。这提供了一种简单而一致的错误处理方案,如果正确完成,它可以使代码更加清晰。 其中一个很大的区别是异常会强制您处理错误,而错误返回代码可能会被取消选中。 错误返回代码,如果大量使用,也会导致非常难看的代码,如果测试类似于此形式:
就个人而言,我更喜欢使用调用代码应该或必须对其执行的错误的异常,并且只使用错误代码来表示"预期失败",其中返回的内容实际上是有效且可能的。 有很多理由更喜欢Exceptions而不是返回代码:
我担心异常的一件事是抛出异常会搞砸代码流。例如,如果你这样做
或者甚至更糟的是,如果我删除了一些我不应该拥有的东西,但是在我完成剩余的清理工作之前就抓住了。投掷给很差的用户恕我直言。 我只使用例外,没有返回代码。我在这里谈论Java。
我遵循的一般规则是,如果我有一个名为 我有一套简单的规则: 1)使用返回代码来处理您希望直接调用者做出反应的事情。 2)对范围更广的错误使用异常,并且可以合理地预期由调用者之上的许多级别处理,以便错误的意识不必渗透到多层,使代码更复杂。 在Java中我只使用未经检查的异常,检查异常最终只是另一种形式的返回代码,根据我的经验,方法调用可能"返回"的二元性通常更多的是阻碍而不是帮助。
我没有发现返回代码比异常更难看。除了 无论如何,如果可能,您必须处理错误。您可以轻松地将异常传播到顶层,因为忽略返回代码并让程序发生段错误。 我喜欢为结果返回值(枚举?)和异常情况的异常。 我在异常与返回代码参数中的一般规则:
对于像Java这样的语言,我会使用Exception,因为如果不处理异常,编译器会给出编译时错误。这会强制调用函数处理/抛出异常。 对于Python,我更加矛盾。没有编译器,因此调用者可能无法处理导致运行时异常的函数抛出的异常。如果使用返回代码,如果处理不当,则可能会出现意外行为,如果使用异常,则可能会遇到运行时异常。 |