错误处理-程序是否因错误而失败或静默忽略它们


20

我正在编写一个简单的小程序来通过网络传输MIDI。我知道程序会遇到传输问题和/或其他无法预测的异常情况。

对于异常处理,我看到两种方法。我应该编写程序以便它:

  • 发生问题时失败并发出爆炸声, 或者
  • 它是否应该忽略错误并继续进行,以牺牲数据完整性为代价?

用户合理地期望哪种方法?
有没有更好的处理异常的方法?

另外,我是否处理网络连接(即,我可以合理预期出现问题的地方)是否会影响我处理异常的决定?


1
@ GlenH7 支付它前进... :)
蚊蚋

有+1编辑的方法吗?;)哦,等等,我追踪您。当然可以,:)
Arlen Beiler

2
“用户会合理地期望哪种方法?”。您是否尝试过问一位用户?走廊可用性测试可以非常有效。参见blog.openhallway.com/?p=146
Bryan Oakley

我没有任何用户:)
Arlen Beiler 2013年

Answers:


34

绝对不要忽略程序遇到的错误。至少,您应该将其记录到文件或其他一些机制中以进行通知。有可能是偶尔的情况下,您会想忽略错误,但记录吧!不要在catch没有任何注释解释任何空白的原因的情况下写一个空白块。

程序是否应该失败取决于上下文。如果可以正常处理错误,请继续处理。如果是意外错误,则您的程序将崩溃。这几乎是异常处理的基础。


18
不足以保证为-1,但是“从不”是一个强词,在某些情况下,忽略错误是正确的做法。例如,用于解码广播HDTV传输的软件。当您经常遇到一些错误时,您所能做的就是忽略它,并继续解码之后发生的事情。
whatsisname 2013年

1
@whatsisname:是,但是您应该清楚地说明为什么忽略它。更新了答案以考虑您的评论。
marco-set

1
@ marco-fiset我不同意。仅当重新启动程序可以解决该问题时,崩溃整个程序才是解决方案。并非总是如此,因此崩溃通常是毫无意义的,并且默认行为是愚蠢的。为什么由于局部错误而要暂停整个操作?这是没有意义的。大多数应用程序都这样做,并且由于某些原因,程序员对此完全满意。
MaiaVictor

@Dokkat:重点是不要继续使用错误的值,这将给出错误的解决方案(垃圾进,垃圾出)。崩溃确实解决了这个问题:它迫使用户要么提供不同的输入,要么使开发人员纠缠于他们的程序修复;)
AndréParamés2013年

1
@whatsisname我想您可能需要考虑这种情况下“错误”的定义。接收到的数据错误与软件程序的结构和执行错误不同。
joshin4colours

18

您永远不应默默地忽略错误,因为您的程序是建立在一系列操作之上的,这些操作隐含地依赖于正确执行操作之前的所有操作。如果在第3步中出现问题,并且您尝试继续进行第4步,则第4步将基于无效的假设开始,这很可能最终也会产生错误。(如果您也忽略了它,那么第5步将引发错误,然后事情就会从那里滚雪球。)

问题是,随着错误的累积,最终您会遇到一些错误,以至于您无法忽略它,因为它将由提供给用户的某些内容组成,而某些内容将是完全错误的。然后,您会有用户抱怨您的程序无法正常工作,因此您必须对其进行修复。并且如果“向用户提供某些东西”部分在步骤28中,并且您不知道导致所有此类混乱的原始错误是在步骤3中,因为您在步骤3中忽略了该错误,那么您将拥有一个一次调试该问题!

另一方面,如果第3步中的错误使用户的脸都炸了,并生成了一条错误消息SOMETHING WENT BADLY WRONG IN STEP 3!(或类似的技术术语,即堆栈跟踪),那么结果是一样的-用户向您抱怨该程序无法正常工作-但这次您确切地知道要修复它的位置

编辑:作为对评论的回应,如果出现您预期的错误并知道如何处理,那是不同的。例如,在收到格式错误的消息时,这不是程序错误;那是“用户提供了错误的输入,导致验证失败。” 适当的响应是告诉用户他给您的输入无效,这听起来像您在做什么。在这种情况下,无需崩溃并生成堆栈跟踪。


如何返回到上一个已知的好位置,或者在接收循环的情况下,仅丢弃该消息并转到下一个消息。
阿伦·贝勒

@Arlen:即使那样可能也是个坏主意。发生错误是因为您在编写代码时发生了某些意外的事情。这意味着您所有的假设都超出了预期。例如,如果您正在修改某些数据结构,而现在处于不一致状态,那么“最后一个已知的良好位置”可能就不再适用。
梅森惠勒2013年

如果期望的话该怎么办,例如防止反序列化的损坏邮件。在某些情况下,我已将异常序列化并通过电线发送回该异常。在那里,查看我对问题的编辑。
阿伦·贝勒

@Arlen:如果您确实预料到了这一点,并且确定您知道错误的后果是什么并且正确地包含了这些错误,那么情况就不同了。在我看来,您正在处理错误并做出适当的响应,而不是忽略它。我说的是忽略意外错误,这就是您在问的问题。
梅森·惠勒2013年

16

在“爆破”和“忽略”之间还有其他选择。

如果错误是可预见和可避免的,请更改设计或重构代码以避免该错误。

如果错误是可预测的,但无法避免,但是您知道发生错误时该怎么办,那么请抓住错误并处理情况。但是要注意避免将异常用作流控制。此时,您可能希望记录警告,并可能在将来采取某种措施来避免这种情况时通知用户。

如果错误是可预测的,不可避免的,并且在发生错误时您无法采取任何措施来保证数据的完整性,那么您需要记录错误并退回到安全状态(正如其他人所说的,这可能意味着崩溃)。

如果错误不是您所预期的,那么您就无法确定自己甚至可以退回到安全状态,因此最好记录并崩溃。

通常,除非您计划记录并重新抛出异常,否则不要捕获任何您无能为力的异常。并且在极少数情况下不可避免地会忽略try-catch,至少在您的catch块中添加注释以说明原因。

有关对异常进行分类和处理的更多建议,请参见Eric Lippert的出色的异常处理文章


1
我认为,迄今为止最好的答案。
2013年

6

这些是我对这个问题的看法:

一个好的开始原则是快速失败。具体来说,对于任何您不知道确切原因的故障,都不要编写错误处理代码。

应用此原理后,您可以为遇到的特定错误情况添加恢复代码。您还可以引入几个“安全状态”以返回到。中止程序通常是安全的,但是有时您可能想返回另一个已知的良好状态。一个示例是现代OS如何处理有问题的程序。它只会关闭程序,不会关闭整个操作系统。

通过快速,缓慢地覆盖越来越多的特定错误条件,您将永远不会破坏数据完整性,也不会稳定地朝着更稳定的程序发展。

吞咽错误,即试图为您不知道确切原因的错误进行计划,因此没有特定的恢复策略,只会导致程序中跳过错误和规避代码的数量增加。由于无法相信先前的数据已正确处理,因此您将开始看到分散检查,以检查不正确或丢失的数据。您的圈复杂度将一发不可收拾,最终您将陷入泥潭。

是否知道故障案例的重要性较小。但是,例如,如果您要处理的网络连接知道一定数量的错误状态,请推迟添加错误处理,直到您还添加恢复代码。这符合上述原则。


6

您永远不应默默地忽略错误。特别是不以牺牲数据完整性为代价

该程序正在尝试执行某些操作。如果失败,则必须面对事实并对此采取措施。那将是什么取决于很多事情。

最后,用户要求该程序执行某项操作,并且该程序应告诉他们该操作未成功。它有许多方法可以做到这一点。它可能会立即停止,甚至可能回滚已经完成的步骤,或者另一方面,它可以继续执行并完成所有可能的步骤,然后告诉用户这些步骤成功而其他步骤则失败。

您选择哪种方式取决于步骤之间的关联程度以及以后所有步骤是否会再次发生该错误,而这又取决于确切的错误。如果需要强大的数据完整性,则必须回滚到上一个一致的状态。如果您只是复制一堆文件,则可以跳过一些文件,最后告诉用户无法复制这些文件。您不应默默地跳过文件,不要告诉用户任何信息。

广告编辑的唯一不同之处在于,您应该考虑放弃一些次然后告诉用户它不起作用,因为网络很可能会出现暂时性错误,如果再次尝试,该错误不会再次发生。


您是否真的要在第一行的末尾打一个问号?
CVn

@MichaelKjörling:否。复制粘贴错误(从问题中复制了公式并错误地包含了问号)。
2013年

4

在一类情况下,忽略错误是正确的事:当对这种情况无法采取任何措施时,或者不良和可能不正确的结果总比没有结果要好。

为了显示目的而解码HDMI流的情况就是这种情况。如果流不好,那就不好了,大喊大叫不会神奇地解决它。您尽力显示它,让查看者确定它是否可以接受。


1

我不认为程序遇到问题时应该默默地忽略或造成破坏。

我为公司编写的内部软件的用途...

它取决于错误,可以说,如果它是向MySQL输入数据的关键功能,则需要让用户知道它失败了。错误处理程序应尝试收集尽可能多的信息,并向用户提供有关如何自行纠正错误的想法,以便他们可以保存数据。我还想提供一种方法,以静默方式将要保存的信息发送给我们,因此,如果情况变得更糟,我们可以在错误修复后手动输入。

如果它不是关键功能,那可能会出错并且不会影响他们想要实现的最终结果,那么我可能不会向他们显示错误消息,而是让它发送一封电子邮件,将其自动插入到我们的错误跟踪软件中或一个电子邮件分发组,该组将警告公司中的所有程序员,以便即使用户未意识到该错误,我们也可以知道。这使我们可以固定后端,而前端则没人知道发生了什么。

我试图避免的最大事情之一是在错误发生后使程序崩溃-无法恢复。我总是尝试为用户提供继续操作而不关闭应用程序的选项。

我相信,如果没人知道这个错误-它永远不会得到修复。我还是错误处理的坚定支持者,一旦发现错误,该错误处理将使应用程序继续运行。

如果错误与网络有关-为什么在执行该功能之前,这些功能为什么不执行简单的网络通讯测试,从而避免该错误呢?然后仅警告用户无法连接,请验证您的互联网等,然后重试?


1
在运行关键功能之前,我本人进行了很多验证,以尝试限制我不完全了解并且无法提供返回详细信息的有用错误处理的错误。它不可能100%地起作用,但是我不记得上次遇到一个使我迷失了几个小时的错误的原因。
杰夫

1

我自己的策略是区分编码错误(错误)和运行时错误,并尽可能使编码错误难以创建。

错误需要尽快修复,因此按合同设计方法是合适的。在C ++中,我喜欢用函数顶部的断言检查我的所有前提条件(输入),以便尽快检测到该错误并使其易于附加调试器并修复该错误。如果开发人员或测试人员选择继续尝试运行该程序,那么任何数据完整性损失都将成为他们的问题。

并首先找到预防该错误的方法。严格遵守const正确性并选择适合于它们将要保存的数据的数据类型是两种难以创建bug的方法。快速安全在需要恢复的安全关键代码之外也很不错。

对于无错误代码可能发生的运行时错误,例如网络或串行通信故障或文件丢失或损坏,请执行以下操作:

  1. 记录错误。
  2. (可选)尝试以静默方式重试或从操作中恢复。
  3. 如果操作继续失败或无法恢复,请向用户显示该错误。然后,如上所述,用户可以决定要做什么。请记住“最少惊讶原则”,因为除非您提前警告了数据完整性,否则对于用户而言这是令人惊讶的。

0

如果您有理由认为程序的整体状态不稳定,并且从现在开始运行,则可能会发生不良情况,因此失败是正确的选择。当您确定无法执行当前操作时,某种程度上可以“忽略”它(例如,正如其他人指出的那样,将其记录到某处或向用户显示错误消息,然后继续)是可以的。继续运行。

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.