如何避免引发恼人的异常?


21

阅读Eric Lippert 关于异常文章绝对是我应该以生产者和消费者的方式处理异常问题的开阔视野。但是,我仍在努力制定有关如何避免引发令人讨厌的异常的准则。

特别:

  • 假设您有一个Save方法可能会失败,因为a)有人在您之前修改了该记录,或者b)您要创建的值已经存在。这些条件是预料之中的,并非例外,因此,您不会抛出异常,而是决定创建方法的Try版本TrySave,该方法返回一个布尔值,指示保存是否成功。但是,如果失败了,消费者将如何知道问题出在哪里呢?还是最好返回一个表示结果的枚举,例如Ok / RecordAlreadyModified / ValueAlreadyExists?使用integer.TryParse时不存在此问题,因为该方法失败的原因只有一个。
  • 前面的例子真的令人烦恼吗?还是在这种情况下抛出异常是首选方式?我知道大多数库和框架(包括Entity框架)都是这样做的。
  • 您如何决定何时创建方法的Try版本,以及如何提供某种方法进行事前测试的方法?我目前正在遵循以下准则:
    • 如果有可能发生竞争,请创建一个Try版本。这避免了消费者需要捕获外部异常。例如,在前面描述的Save方法中。
    • 如果测试条件的方法几乎可以完成原始方法的所有工作,则创建一个Try版本。例如,integer.TryParse()。
    • 在任何其他情况下,请创建一种测试条件的方法。

1
您可能会失败的保存示例实际上并不是一个非常令人烦恼的异常。这很普通,可能应该只是一个例外。
S.Lott 2012年

@ S.Lott:这很普通是什么意思?情况本身,还是在这种情况下引发异常?无论如何,我同意你的看法,这是否确实是一个令人不快的局面,还不是很明显。我将更新问题。
Mike

“情况本身,或在这种情况下引发异常”两者。
S.Lott 2012年

Answers:


24

假设您有一个Save方法可能会失败,因为a)有人在您之前修改了记录,或者b)您要创建的值已经存在。这些条件是预料之中的,并非例外,因此,您不会抛出异常,而是决定创建方法的Try版本TrySave,该方法返回一个布尔值,指示保存是否成功。但是,如果失败了,消费者将如何知道问题出在哪里?

好问题。

我想到的第一个问题是:如果数据已经存在,那么保存在什么意义上失败了听起来好像成功了。但是,为了争辩,我们假设您确实有许多原因导致操作失败。

我想到的第二个问题是:您希望返回给用户的信息是否可行 也就是说,他们将基于该信息做出一些决定吗?

当“检查引擎”指示灯亮起时,我打开引擎盖,核实汽车中是否有未着火的引擎,并将其带到车库。当然,在车库里,他们有各种专用的诊断设备,可以告诉他们为什么检查引擎灯亮着,但是从我的角度来看,警告系统设计得很好。我不在乎问题是因为氧气传感器在燃烧室中记录了异常的氧气水平,还是因为怠速检测器未插好,或其他原因。我将采取相同的操作,即让其他人弄清楚这一点

呼叫者是否在意保存失败的原因?除了放弃或重试之外,他们是否会对此做任何事情?

为了便于讨论,我们假设调用者实际上将根据操作失败的原因采取不同的操作。

想到的第三个问题是:失败模式是否例外我想你可能会混淆可能平常。我会想到两个用户试图同时修改同一条记录是一种例外但可能的情况,而不是常见的情况。

为了争论起见,我们假设它是无例外的。

想到的第四个问题是:是否有办法提前可靠地检测到不良情况?

如果糟糕的情况出在我的“外部”存储桶中,那就没有。无法可靠地说“其他用户是否修改了该记录?” 因为他们可能会在您提出问题后对其进行修改。答案一经产生就过时

想到的第五个问题是:有没有一种设计API的方法,可以防止出现这种情况?

例如,您可以使“保存”操作需要两个步骤。第一步:获取正在修改的记录的锁。该操作成功或失败,因此可以返回布尔值。然后,呼叫者可以制定有关如何处理失败的策略:稍等片刻,然后重试,放弃。第二步:获取锁后,进行保存并释放锁。现在保存总是成功的,因此无需担心任何类型的错误处理。如果保存失败,那确实是例外。


所有非常好的观点,谢谢。现在,这是一个关于我的文章的总结性问题:如果您要重新设计File.Open(),您是否会创建File.TryOpen()?您将如何与消费者沟通失败的原因?还是引发外来异常真的是这里最好的折衷方案?
Mike

10
@Mike:文件系统是使用外部异常的一个很好的例子。他们很少失败,所以失败是例外。它们无法预料地失败,并且由于某些原因完全超出了呼叫者的控制范围(没有“锁”可让您插入以太网电缆),并且失败既多样又可操作(也就是说,失败是因为文件被锁定)。找不到文件还是找到文件,但是您没有写访问权限,两者都可以以不同的方式执行。)所有这些都是将失败表示为异常的原因。
埃里克·利珀特

我确实认为现在可以回答这个问题,但是我很好奇;)如果该方法由于两个或更多原因而失败,则该失败是可操作的,这些失败是异常的,并且这些失败无法提前被发现或无法避免,你会怎么做?
迈克

@麦克我不能代表埃里克(Eric),但这听起来像是错误代码的好地方。也许返回一个枚举成员。
马修(Matthew)

1

在您的示例中,如果可以轻松地检查ValueAlreadyExists情况,则应在进行保存之前检查它并引发异常,我认为在这种情况下不必尝试。竞赛条件很难提前检查,因此在这种情况下将Save打包为Try可能是一个很好的主意。

通常,如果我认为很可能存在某种情况(例如NoDataReturned,DivideByZero等)或很容易检查(例如空集合或NULL值),我会尝试检查在我到达必须捕获异常的地步之前,请提前解决它。我承认提前知道这些条件并不总是那么容易,有时它们仅在代码经过严格测试时才会出现。


0

save()方法必须引发异常。

最顶层应该捕获并通知用户,而不终止程序,除非它是Unix之类的命令行程序,在这种情况下可以终止。

返回值不是管理异常的好方法。

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.