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

时间:2008-09-25 17:25:53

标签: .net sql sql-server exception-handling

我一直觉得期望定期抛出异常并将它们用作流逻辑是一件坏事。例外情况应该是“例外”。如果你期望并计划一个例外,这似乎表明你的代码应该被重构,至少在.NET中...... 然而。最近的一个场景让我停下来。我刚刚在msdn上发布了这个,但是我想对它进行更多的讨论,这是一个完美的地方!
所以,假设你有一个数据库表,其中有几个其他表的外键(在最初提示辩论的情况下,有4个外键指向它)。您希望允许用户删除,但仅限于没有外键引用;你不想级联删除。
我通常只是检查是否有任何引用,如果有,我通知用户而不是删除。在LINQ中编写它是非常容易和轻松的,因为相关表是对象的成员,所以Section.Projects和Section.Categories等等很好用intellisense输入所有... 但事实是,LINQ必须打击所有4个表,看看是否有任何结果行指向该记录,并且点击数据库显然总是一个相对昂贵的操作。

这个项目的负责人让我把它改成只捕获一个代码为547(外键约束)的SqlException并以这种方式处理它。

我是......

但是在这种情况下,吞下与异常相关的开销可能比吞下4个表命中更有效...特别是因为我们必须在每种情况下进行检查,但我们在这种情况下没有例外什么时候没有孩子...
此外,数据库确实应该是负责处理参照完整性的人,这是它的工作,它做得很好......
所以他们赢了,我改变了。

在某种程度上,它仍然感觉错误对我来说
你们对期望和故意处理异常有什么看法?它看起来比预先检查更有效吗?对于下一个查看代码的开发人员来说,更令人困惑的是它更令人困惑吗?它是否更安全,因为数据库可能知道开发人员可能不会考虑添加检查的新外键约束?或者是关于你认为最佳实践究竟是什么的透视问题?

8 个答案:

答案 0 :(得分:7)

你的领导是绝对正确的。例外不仅适用于蓝月情况下的一次,而且特别适用于除预期结果之外的报告。

在这种情况下,仍会进行外键检查,例外是通知您的机制。

你不应该做的是用一条毯子捕获语句来捕捉和抑制异常。进行细粒度的异常处理特别是为什么首先设计了异常。

答案 1 :(得分:4)

哇,

首先,你可以请稍微提炼一下这个问题,虽然阅读一个经过深思熟虑和解释的问题很好,这是很多要消化的。

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

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

在我看来:

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

我知道有一个共识,即除了特殊情况之外不应该使用异常,但请记住,我们在这里跨越应用程序边界,期待什么。就像我说的那样,这就像File.Exists,没有任何意义,无论如何都可以删除它。

答案 2 :(得分:2)

我认为你是对的,异常只应用于处理意外的结果,在这里你使用异常来处理可能预期的结果,你应该明确地处理这个案例但仍然抓住显示可能错误的例外。

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

答案 3 :(得分:2)

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

答案 4 :(得分:1)

你在做什么并没有错。例外并非毫无疑问“特殊”。它们的存在是为了允许调用对象根据需要进行细粒度的错误处理。

答案 5 :(得分:1)

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

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

答案 6 :(得分:1)

我建议调用存储过程检查是否存在依赖关系,然后删除,如果没有依赖关系。这样就完成了对完整性的检查。

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

答案 7 :(得分:1)

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

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

另一点是,这种“短程”异常不会导致维护问题,也不会使您的程序更难以阅读。您将拥有try,要删除的SQL调用,以及10行代码中的所有内容。意图很明显。

不喜欢抛出一个异常,这个异常将被捕获到堆栈中的5个过程调用上面,并且确保其间的所有对象都已正确处理(释放)。

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

我的两分钱,

伊夫