捕获异常作为预期的程序执行流控制?

Posted

技术标签:

【中文标题】捕获异常作为预期的程序执行流控制?【英文标题】:Catching exceptions as expected program execution flow control? 【发布时间】:2010-09-13 04:16:08 【问题描述】:

我一直觉得期望定期抛出异常并将它们用作流逻辑是一件坏事。异常感觉应该是“exception”。如果您期待并计划出现异常,这似乎表明您的代码应该被重构,至少在 .NET 中... 然而。最近的一个场景让我停下来。我不久前在 msdn 上发布了这个,但我想引起更多关于它的讨论,这是一个完美的地方! 因此,假设您有一个数据库表,其中有几个其他表的外键(在最初引发辩论的情况下,有 4 个外键指向它)。您希望允许用户删除,但前提是没有外键引用;你不想级联删除。 我通常只是检查是否有任何引用,如果有,我会通知用户而不是删除。在 LINQ 中编写它非常容易和轻松,因为相关表是对象上的成员,因此 Section.Projects 和 Section.Categories 等很适合使用智能感知和所有... 但事实是 LINQ 可能必须访问所有 4 个表以查看是否有任何结果行指向该记录,并且访问数据库显然总是一个相对昂贵的操作。 该项目的负责人要求我将其更改为仅捕获代码为 547(外键约束)的 SqlException 并以这种方式处理它。 我……抗拒。 但是在这种情况下,吞下与异常相关的开销可能比吞下 4 个表命中要有效得多……尤其是因为我们必须在每种情况下都进行检查,但我们可以避免这种情况下的异常没有孩子的时候... 另外,数据库确实应该负责处理引用完整性,这是它的工作,它做得很好...... 所以他们赢了,我改变了它。 但在某种程度上,我仍然觉得 。 你们如何看待期待和有意处理异常?当它看起来比事先检查更有效率时可以吗?下一个查看您的代码的开发人员是否更容易混淆,或者更容易混淆?是否更安全,因为数据库可能知道开发人员可能不会考虑添加检查的新外键约束?还是您认为最佳实践是什么?

【问题讨论】:

【参考方案1】:

你的领导是绝对正确的。例外不仅是在蓝月亮的情况下出现一次,而是专门用于报告预期结果之外的结果。

在这种情况下,仍然会进行外键检查,并且可以通过异常机制通知您。

您不应该做的是使用笼统的笼统声明来捕获和抑制异常。进行细粒度的异常处理就是首先设计异常的原因。

【讨论】:

嗯,这是预期的结果,这就是问题所在。很多时候,会有孩子。 我非常同意您的编辑。具体来说,只有 SqlException 547 被捕获 在这一点上完全同意你的看法,用这种“特殊情况”回复的人没有抓住重点(见我的帖子)。【参考方案2】:

哇,

首先,请您稍微提炼一下这个问题,虽然很高兴阅读一个经过深思熟虑和解释的问题,但要消化的内容很多。

简短的回答是“是”,但可能取决于。

我们有一些应用程序,其中我们在 SQL 查询中绑定了很多业务逻辑(不是我的设计 Gov!)。如果这是它的结构方式,管理层可能很难说服其他方式,因为它“已经奏效”了。 在这种情况下,真的很重要吗?因为它仍然是一次穿越电线和回来的旅行。服务器在意识到它无法继续之前是否做了很多事情(即,如果您的操作发生了一系列事务,那么它是否会在中途失败,浪费时间?)。 首先在 UI 中进行检查有意义吗?它对您的申请有帮助吗?如果它提供更好的用户体验? (即,我曾看到您在向导中逐步完成几个步骤的情况,它会启动,然后跌倒,当它拥有在第 1 步之后跌倒所需的所有信息时)。 并发是个问题吗?是否有可能在您的提交发生之前删除/编辑记录或进行任何其他操作(如经典的File.Exists boo-boo)。

在我看来:

我会两者都做。如果我能快速失败并提供更好的用户体验,那就太好了。无论如何,任何预期的 SQL(或任何其他)异常都应该被捕获并适当地反馈。

我知道大家一致认为,除了异常情况之外,不应使用异常,但请记住,我们在这里跨越了应用程序边界,不要期待。就像我说的,这就像File.Exists,没有意义,反正在你访问之前可以删除它。

【讨论】:

这是一个非常小规模的应用程序;在客户当前的环境中,大部分的插入将只由一个人完成。我认为最多有 25 个用户。所以并发不是问题,性能不是问题,现在是讨论标准的最佳时机;) 但这不仅仅关乎性能或并发性。它的假设是问题所在。就像我说的,如果能提供更好的用户体验,就两者都做,但无论哪种情况,你都应该处理异常。【参考方案3】:

我认为你是对的,异常应该只用于处理意外的结果,这里你使用异常来处理可能预期的结果,你应该明确地处理这种情况但仍然捕获显示可能的错误的异常。

除非这是在整个代码中处理这种情况的方式,否则我会支持你。只有当它确实是一个问题时才应该提出性能问题,即它取决于这些表的大小和使用此函数的次数。

【讨论】:

【参考方案4】:

好问题。但是,我找到了答案……可怕! 一个例外是一种 GOTO。 我不喜欢以这种方式使用异常,因为这会导致意大利面条式代码。 就那么简单。

【讨论】:

【参考方案5】:

你所做的并没有错。例外不一定是“例外”。它们的存在是为了允许调用对象根据需要进行细粒度的错误处理。

【讨论】:

【参考方案6】:

捕获特定的 SqlException 是正确的做法。这是 SQL Server 传递外键条件的机制。即使您可能更喜欢异常机制的不同用法,SQL Server 也是这样做的。

此外,在您检查这四个表期间,其他用户可能会在您完成检查之前但在您阅读该表之后添加相关记录。

【讨论】:

【参考方案7】:

我建议调用一个存储过程来检查是否存在任何依赖关系,如果没有则删除。这样,完整性检查就完成了。

当然,对于单例删除和批量删除,您可能需要不同的存储过程...批量删除可以查找子行并返回一组不符合批量删除条件的记录(有子行)。

【讨论】:

【参考方案8】:

我不喜欢在程序中到处看到异常,但在这种情况下我会使用异常机制。

即使您在 LINQ 中进行测试,您也必须捕获异常,以防有人在您使用 LINQ 测试完整性时插入子记录。既然你无论如何都要检查异常,为什么要重复代码?

另外一点是,这种“短程”异常不会导致维护问题,也不会使您的程序更难阅读。您将在 10 行代码内完成 try、删除的 SQL 调用和 catch 操作。意图很明显。

不像抛出要在堆栈上层的 5 个过程调用中捕获的异常,其问题是确保中间的所有对象都已正确处理(释放)。

异常并不总是一个神奇的答案,但在这种情况下使用它们我不会觉得错。

我的两分钱,

伊夫

【讨论】:

以上是关于捕获异常作为预期的程序执行流控制?的主要内容,如果未能解决你的问题,请参考以下文章

Java 异常的捕获与处理详解

由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“线程违规:预期主线程”

在 spark 中捕获已执行 sql 的异常

JVM-JVM 是如何处理异常的

异常+异常链

Kotlin 协程Flow 流收尾工作 ( finally 代码块收尾 | onCompletion 代码块收尾 | onCompletion 中获取异常信息 | catch 代码块中捕获异常 )