我应该在BLL、DAL还是PL中处理异常?

3
哪个地方最适合处理异常?BLL、DAL还是PL?
我应该允许DAL和BLL中的方法将异常抛到链上并让PL处理它们吗?还是应该在BLL中处理它们?
例如:
如果我在我的DAL中有一个方法发出"ExecuteNonQuery"并更新一些记录,由于一个或多个原因,0行受到影响。现在,我应该如何让我的PL知道是否发生了异常,或者是否真的没有匹配到条件的行。我应该在我的PL代码中使用"try catch"并通过异常来让它知道,还是应该在DAL中处理异常并返回一些特殊的代码(如-1),以便让PL区分(异常)和(没有匹配条件即零行受影响)之间的区别?

0行受影响不会引发异常。你的问题真的是“当0行受影响时,我应该抛出异常吗?”我认为对此你会得到不同的答案。 - mbeckish
1
@mbeckish:是的,但那些说你应该(总是)这样做的人是错误的!(除非您的程序在查询“0行受影响”之后无法正常运行,并且这是由于意外情况(而不是无效输入)引起的,那么您应该抛出一个带有相关信息的应用异常(或类似), 但这只是一种边缘情况)。 - FastAl
@FastAl - 我并不是在提倡那种做法,只是想澄清问题。 - mbeckish
1
异常应该是特殊的!查询数据时找不到行并不意外。此外:引发错误会导致性能下降,因此如果没有必要,最好不要这样做。 - Adrian K
7个回答

5

让在DAL中引发的异常传播到PL是没有意义的 - 如果无法建立数据库连接,用户应该如何反应?

尽早捕获和处理异常,如果你能够处理它们。不要仅仅将它们吞下而不输出提示或日志信息 - 这将导致严重的困难和难以跟踪的错误。


准备好和我一起被踩了吧...这个策略今天似乎不太受欢迎 :) - joshlrogers
我不会给你点踩...但是...有些错误在较低级别处理是可以的。这不是一个硬性规定,而是一种心态。但是没有数据库连接?即使更改消息,PL也必须知道。 关于不吞噬它们 - 阿门!任何人都不应该那样做。最佳实践还要求始终记录ex.tostring而不仅仅是消息。并且具有带有行号的PDB(即使这很困难,您需要对抗安全/政治/技术问题)。因此,在日志中完整的堆栈跟踪。不同的机器?仍然必须保留innerexceptions,这是可能的。 - FastAl
@joshlrogers 我就是不明白为什么你不想区分错误条件和没有返回数据之间的区别。我想我们只能同意不同意了。 - s1mm0t
@s1mm0t:他并没有说他不想区分错误条件和缺失数据!他只是建议在无法建立数据库连接时通知用户,因为如果真的没有连接,用户自己也无能为力... - Marius Schulz
显示剩余2条评论

3
简短的回答是,这取决于情况!
只有在能够对异常进行有用处理时才应该处理它。而“有用的处理”又取决于你正在做什么。你可能想要记录异常的详细信息,虽然这并不是真正的处理方式,在大多数情况下,你应该在记录日志后重新抛出异常。你可能想要将异常包装在其他一些(可能是自定义的)异常中,以便向异常添加更多的信息。正如 @mbeckish 所提到的,你可能想尝试通过重试操作来从异常中恢复 - 但是你应该小心不要永远重试。最后(请原谅双关语),你可能想要使用 finally 块来清理任何资源,比如一个打开的数据库连接。你选择如何处理异常将影响你在哪里处理它。很可能许多异常并没有太多有用的处理方式,除了向用户报告发生了错误,这种情况下在 UI 层处理异常并向用户报告问题是可以接受的(你应该在更低层次记录异常)。
当你自己抛出异常时,你应该只在“异常”的情况下抛出异常,因为抛出异常有很大的开销。在你的例子中,如果你的操作未更新任何记录,你可能会考虑抛出异常。这真的是异常情况吗?在这种情况下,更好的做法是返回更新的记录数 - 这可能仍然是需要向用户报告的错误条件,但不像因为与数据库的连接断开而导致命令失败那样是异常情况。 这篇文章 是关于异常处理最佳实践的一个合理的指南。

如果我将我的异常记录到某个日志源中,例如在数据访问层(DAL)的某个方法中记录了我的异常,并且该方法从 PL 中的多个位置调用,那么日志如何传达异常在 PL 代码中发生的位置信息? - teenup
@s1mm0t 我可能被误解了。我也不喜欢返回代码,除非你正在处理一些非常特定的情况,并且你有人去/愿意认真跟踪所有潜在的错误代码。错误代码通常是太多开销和过于“企业化”的东西。通常,我的想法是,如果它是要返回的对象,则在异常情况下返回 null;如果它是一个布尔值,则根据预期的功能可能会返回 false;如果它是一个值类型,则通常会重新抛出异常以由调用方法处理。 - joshlrogers
@joshlrogers。仅以你的一个例子为例,虽然我认为这对所有例子都适用,在发生异常时返回NULL的情况下,我觉得这是不正确的。考虑一种从数据库中读取记录的方法。如果该记录不存在,这可能不是“异常”情况,因此应返回NULL。这与调用此方法并且连接断开的情况非常不同-在这种情况下,不应返回NULL,而应抛出异常。 - s1mm0t
@s1mm0t 我同意应该抛出异常,但为什么BL必须知道异常呢?最终结果是一样的,即没有记录可用,这意味着PL没有任何东西可以显示。如果您没有任何类型的日志记录,那么是的,您几乎需要将其冒泡以便有人被通知,但是如果您充分记录异常,则异常将保存到数据库/文件等中,用户不会受到异常消息的攻击。我还没有找到一个让我的BL关心处理SQLException或类似情况的论据。 - joshlrogers
@s1mm0t 我主张保持层之间的独立性。各层应该只了解彼此所需的最少信息以确保其正常运行。每个层应尽力处理自身问题并在必要时向调用层发送通知。我知道这是个人偏好的问题,但我不认为我的业务逻辑层或展示层应关心其他层抛出了什么异常,只需要知道失败即可,然后相应地采取措施。如果我的业务逻辑层必须了解数据访问层可能抛出的所有不同异常,对我来说这涉及了太多责任混合。 - joshlrogers
显示剩余5条评论

3
这是一个具有很多不必要争议的重大话题(声音响亮且提供错误信息的人!),如果你愿意处理它,遵循 s1mm0t 的建议,这在大多数情况下是可以接受的。 但是,如果你想要一个一词回答,把它们放在PL中。认真的。 如果可以的话,请将错误处理放在全局异常处理程序中(出于安全原因,所有错误应记录并提供用于在生产中查找日志的代码(特别是Web),但在开发期间为了速度原因应提供完整的详细信息)。除此之外,在处理有限资源项目时,我经常使用“using”关键字或try / finally / end try而没有catch。对于多线程锁/互斥/重新进入防止标志等情况,您还需要在所有情况下都使用try / finally,以便程序仍然正常工作(特别是有状态的应用程序)。如果您使用异常不当(例如,处理非错误性问题时应使用IF语句或在尝试操作之前先检查它是否可疑),则此哲学会更容易崩溃。在厚的客户端应用程序中尤其如此,特别是当存在失去大量用户输入的可能性时,您可能需要更多的try / catches,其中您尝试保存数据(当然标记为尚未有效)。编辑:至少需要在PL中具有日志记录程序的另一个需要-这将根据平台而异。我们正在处理的应用程序与3个PL版本共享BLL / DAL:ASP.Net版本,winforms版本和控制台应用程序批处理回归测试版本。实际上调用的日志记录例程位于BLL中(DAL仅会引发错误或完全处理任何错误或重新引发它们)。但是,这会引发由PL处理的事件;在Web上,它被放置在服务器的日志中,并显示Web风格的错误消息(生产友好消息);在WinForms中,一个特殊的消息窗口出现并显示技术支持信息等,并在后台记录错误日志(开发人员可以做一些“秘密”的事情以查看完整信息)。当然,在测试版本中,这是一个更简单的过程,但也不同。
我不确定我该如何在BLL中完成这项工作,除了传递一个参数“哪个平台”,但由于它不包括日志记录所依赖的winforms或asp库,这仍然是一个技巧。

在我的一个项目中,我处理了所有的异常并将它们仅记录在 PL 中。但这使得 PL 代码非常混乱,真正的逻辑难以阅读。这也是我想要在更低层记录异常的原因之一。 - teenup
如果您使用的是winforms或asp.net,则不必在PL中添加处理程序。使用这些框架提供的全局异常处理程序即可。您可以在全局asp或sub main中设置它,然后每次点击/ UI操作在进入您的代码之前都会有自己的'try catch',并且所有错误都会转到代码中的一个位置。太棒了!是的,存在一些缺点和限制,但它们并非无法克服。 - FastAl

1
知道如何解决问题的层应该处理异常。例如,如果您决定通过重试查询一定次数来处理死锁错误,则可以将其构建到数据访问层(DAL)中。如果仍然失败,则可能希望将异常上升到下一层,然后该层可以决定是否知道如何正确处理此异常。

0
你的应用程序中的所有层都应该优雅地处理异常。这被称为横切关注点,因为它出现在你的所有层中。 我相信使用像Enterprise Exception Block with unity这样的框架,你最终会得到更好的代码。 看看这篇文章。

http://msdn.microsoft.com/en-us/library/ff664698(v=PandP.50).aspx

掌握它需要一些时间,但有许多示例和屏幕录像可供参考。


0
如何处理异常取决于技术和业务需求。对于复杂或非常重要的数据库更新,我包括输出参数,将一小部分已知错误列表传递回DL。这样,在某些情况下,已知的错误场景可以通过编程方式解决。在其他情况下,需要记录错误并通知用户发生了错误。
我习惯通知人类存在错误。当然,记录会给我们提供详细信息,但它不能替代人类的响应时间。不仅如此,为什么要强迫开发人员观看系统日志以查看事情是否出现问题呢?谈论不必要的成本。
如果您有时间定义潜在的错误/异常并以编程方式解决它们,那么请尽管这样做。许多时候,错误/异常是意外的。这就是为什么准备好应对意外情况非常重要,最好的方法就是涉及到人类。
总体而言,在规划异常处理时应保持防御性。程序会增长或死亡。增长的一部分是引入错误。因此,不要试图消灭所有错误。

-2

问题是,异常在哪里才是相关的?如果是数据访问异常,应该在 DAL 中捕获。如果是逻辑异常,则应在 BLL 中捕获。如果是表示异常,则在 PL 中。

例如,如果您的 DAL 抛出异常,它应该返回 null 或 false 或任何适用的情况给您的 BLL。如果 DAL 返回 null,您的 BLL 应该知道该怎么做,也许直接通过,也许尝试调用另一个函数等等。如果 BLL 从 DAL 传递 null 或返回自己特定的内容,则表示层应该能够通知最终用户存在问题。

当然,您不会得到冗长的异常消息,但就您的用户而言,这是一件好事。您应该有一个灵活的日志记录系统,以捕获这些异常并将其报告给数据库或 ip:port 或您决定的任何其他位置。

本质上,您需要考虑 关注点分离,如果关注点是数据问题或逻辑问题,则应相应地处理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接