12

我一直觉得期望定期抛出异常并将它们用作流逻辑是一件坏事。异常感觉它们应该是“异常”。如果您期望并计划出现异常,那似乎表明您的代码应该被重构,至少在 .NET 中......
但是。最近的一个场景让我停下来。不久前我在 msdn 上发布了这个,但我想引起更多关于它的讨论,这是一个完美的地方!

因此,假设您有一个数据库表,其中有几个其他表的外键(在最初引发辩论的情况下,有 4 个外键指向它)。您希望允许用户删除,但前提是没有外键引用;你不想级联删除。
我通常只是检查是否有任何引用,如果有,我会通知用户而不是删除。在 LINQ 中编写它非常容易和轻松,因为相关表是对象上的成员,因此 Section.Projects 和 Section.Categories 等很适合用智能感知和所有类型输入......
但事实是 LINQ 然后必须可能会访问所有 4 个表以查看是否有任何结果行指向该记录,并且访问数据库显然总是一个相对昂贵的操作。

该项目的负责人要求我将其更改为仅捕获代码为 547(外键约束)的 SqlException 并以这种方式处理它。

我是……
抗拒的。

但是在这种情况下,吞下与异常相关的开销可能比吞下 4 个表命中要有效得多……尤其是因为我们必须在每种情况下都进行检查,但我们可以避免这种情况下的异常当没有孩子时...
加上数据库确实应该负责处理引用完整性,这是它的工作并且做得很好...
所以他们赢了,我改变了它。

但在某种程度上,我仍然觉得它是错误的。

你们如何看待期待和有意处理异常?当它看起来比事先检查更有效率时可以吗?下一个查看您的代码的开发人员是否更容易混淆,或者更容易混淆?是否更安全,因为数据库可能知道开发人员可能不会考虑添加检查的新外键约束?还是你认为最佳实践的观点是什么?

4

8 回答 8

7

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

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

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

于 2008-09-25T17:29:37.917 回答
4

哇,

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

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

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

在我看来:

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

我知道有一个共识,即异常不应用于除异常情况外,但请记住,我们在这里跨越应用程序边界,不要期待。就像我说的,这就像File.Exists,没有意义,无论如何都可以在访问之前将其删除。

于 2008-09-25T17:37:53.107 回答
3

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

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

于 2008-09-25T17:42:05.343 回答
3

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

于 2008-09-26T13:18:10.780 回答
2

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

于 2008-09-25T17:40:34.257 回答
1

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

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

于 2008-09-25T17:42:35.357 回答
1

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

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

于 2008-09-25T19:32:46.717 回答
1

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

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

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

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

Exceptions are not always a magic answer, but I would not feel wrong to use them in this situation.

My two cents,

Yves

于 2010-11-23T02:54:59.043 回答