业务应用程序的异常概念






4.04/5 (13投票s)
关于面向异常的业务应用程序编程的文章 1。
引言
异常处理常常是一个令人头疼的问题,通常留到太晚才处理,而且往往做得不够好,但其实要做好也并非难事。以下是一些在你下次设计、编码和实现应用程序时可以考虑的想法。
背景
很久以前,我在MSDN上读到了一篇关于面向异常编程的非常有趣的文章,但现在找不到了。我将尝试记录下我还能回忆起的内容,以及我从中学习到的东西。如果这篇文章反响不错,我将尝试在此基础上进行更详细的阐述,并提供一些我个人使用过的示例。
我计划将这篇文章分成2到3部分,具体取决于大家的反馈和回应。
我并非最优秀的.NET开发者,写这篇文章时也未考虑任何特定技术。异常处理,正如我下面描述的,更多的是一种概念,而非一个简单的catch块。无论你使用何种技术开发,只要你需要进行异常处理,本文都可能有所帮助。
交付一个“ OK”的解决方案和一个“出色”的解决方案之间的区别,可能在于正确的异常处理和可行的异常管理。
什么是异常
“异常”一词的意思是“不属于常规操作或标准的操作/事件”,在编程中也同样适用。
异常仅在代码的“正常”流程未被遵循时使用,例如,如果从数据库读取的数据返回了一个与预期值不兼容的值(这通常是一个应用程序异常)。
通常,一个异常可能会引发一个或多个其他相关异常。例如,如果创建客户交易时抛出了一个异常,那么也可能抛出一个业务异常,以表明客户余额可能存在问题。
抛出异常不用于在方法或过程中传递值(例如,返回值),必须使用基本类型或类来完成此操作。
异常的用途非常广泛,我们将重点关注以下几种:
- 应用程序异常
- 安全异常
- 权限异常
- 业务异常
何时抛出异常
可以在代码的任何地方抛出异常,以中止处理并将控制权返回给具有异常处理的第一个调用方法。
抛出异常与从方法返回(中止)之间存在显著差异,例如:
Method1()
{
Statement1(){}
Statement2(){}
Statement3()
{
Method2()
{
Statement1(){}
Statement2(){}
}
}
Statement4(){}
}
如果Method2
的Statement1
返回一个值,控制流将转到Method2
的Statement2
;然而,如果Method2
的Statement1
抛出异常,Method2
的Statement2
(以及Method1
的Statement4
)将永远不会被执行,调用栈将展开直到第一个异常处理器或尽可能高的级别,这可能会导致执行线程/应用程序崩溃。
因此,如果你想中止所有后续语句,就抛出异常;当你完成方法并想要退出时,则不要抛出异常。请记住,之后你的方法可能会被第三方方法调用,抛出不必要的异常可能会给调用功能带来问题。
总结:如果“正常”执行无法继续,则抛出异常;当方法完成处理并需要将控制权返回给调用方法时,则返回一个值(或null
)。
何时捕获异常
经验法则是尽量少做异常处理(毕竟它是开发者设计的),但要记住,在简洁和疏忽之间有一条微妙的界限。
经验法则应扩展为:“仅当你想以下任一操作时才捕获异常:
- 记录它并重新抛出,或
- 从中恢复”
然而,这可能会变得相当令人困惑,需要开发者自行判断。
如果你在一个调用了许多子方法的长方法中,将每个子方法调用包装在异常处理器中可能会很有用,但请记住,只有在你想要记录它或从中恢复时才这样做。
有时在方法中处理异常、记录附加数据并重新抛出它会很有用。
处理异常的两个最常见的地方是:
- 用户界面,以及
- 你的服务层
这两个都需要稳定且高可用,因此在此处处理异常非常重要。
在必要的地方(而不是疏忽)拥有全面的异常处理非常重要。
在服务层,你希望确保每个方法都有足够的异常处理,以确保服务保持可用。确保无论调用方法做什么,服务都将保持可用。
在用户界面中,你希望确保无论用户做什么,应用程序都将保持稳定,通常需要处理每个控件的事件处理程序,因为用户的操作可能会导致异常抛出。
如有疑问,请以逻辑方式处理异常,但如果你编写了大量的异常处理代码块,你可能做得不对。
假设
在整个应用程序代码中都必须做出假设。通常,“主要成功场景”是我们假设一切都正常以完成一个流程,例如,为了完成销售订单,所需的客户和商品都存在。当用户输入无效的客户ID时,我们就有一个必须处理的规则/假设的例外。
另一个例子是,我们假设存储过程返回的数据类型在生产环境中与开发环境中的相同。我们不会测试每个返回值的.数据类型,因为这会增加显著的开销,而且如果.数据类型不匹配,由于某些内容不正确而发生更改,我们肯定会遇到异常。
假设经常被“分支”以适应现实世界的场景,所以与其让异常因为一个Customer
不存在于一个Order
中而被抛出,不如我们先测试Customer
是否存在,并做出相应的响应。
选择分支假设还是处理异常,通常取决于应用程序或服务的具体情况和架构。
为每一种可能的问题分支一个流程将导致你编写的分支代码比流程代码多,所以请确保只在必要时分支。
异常日志记录
异常日志记录的重要性仅次于捕获异常。如果异常未被记录,调试复杂异常将非常困难,并且应用程序的健康状况监控将变得几乎不可能。
在服务或后台处理中,异常日志记录与捕获异常一样重要。在用户界面中,仅显示异常是可以接受的,输入的数据类型验证失败就是一个很好的例子。
将异常信息存储在可集中报告的数据存储中(例如数据库)很重要,但更重要的是将异常记录下来。
在生产环境中,可能会发生数据存储访问的安全配置失效,这可能导致无法在可集中报告的数据存储中记录异常,在这种情况下,拥有第二个数据存储(最好是不同且不那么复杂的技术)来记录异常详细信息非常重要。大多数操作系统都提供本地机器的本地日志,这些本地日志也可以被监控。
记录异常时,记录尽可能多的信息非常重要。当异常附带足够的信息时,调试能力会大大提高。记录业务数据与异常一起通常很有用,但应注意不要在公共可访问的地方记录敏感数据(如用户名和密码,或敏感的HR数据)。为异常日志提供一个唯一的标识符以供将来参考至关重要。标识符通常使用多种参考格式,通常使用全局唯一标识符(GUID),但它可能会让用户感到畏惧,并且由于是一个36个字符的字符串而难以引用,一个唯一的顺序编号通常更实用。
通常最好有多个异常日志,并根据类型拆分日志条目。在高容量环境中,细节可能会在海量数据中丢失,调试可能会受阻。建议将业务异常与应用程序异常分开,例如,因为通常会有不同的人员监控日志。
在某些情况下,需要支持人员能够通过添加解释或解决方案描述来更新异常日志。在生产环境中,绝不应存在大量异常日志,并且管理异常应成为任何环境中的高优先级事项。
应用程序异常
应用程序异常(也视为技术异常)是指与应用程序执行和所使用的技术相关的异常。最常见的应用程序异常可能是“对象引用未找到”,这通常发生在对象实例存在的假设不成立时。另一个常见的应用程序异常是,例如,当数据存储发生更改导致数据类型不再匹配时,我们会得到一个运行时异常,应用程序将无法继续执行。
应用程序异常也是所有异常类型(如业务异常或安全异常)的基础;如果对超级类型异常执行了不足的异常处理,应用程序异常处理也应捕获这些异常。
始终假设可能会发生应用程序异常,并必须建立适当的异常处理机制。
由于应用程序异常通常是技术性的,因此重要的是不要向用户显示所有细节。用户不太可能理解显示的细节,并会建立对应用程序的恐惧/抵触情绪。应用程序异常应被记录下来,并向用户提供一个唯一的日志参考号,以便将来与支持人员沟通时使用。支持电话也可以自动记录此参考号。
以下详细信息应作为记录应用程序异常的最低要求:
- 应用程序名称,或对异常源自的服务或后台处理器的良好引用。尽可能多的应用程序/进程版本信息。
- 发生异常的方法名称,以及尽可能详细的调用堆栈信息。
- 异常发生时执行进程的计算机。
- 异常发生的精确日期和时间。这应该是异常发生的时间,而不是日志条目创建的时间,如果两者之间存在显著差异。
- 日志条目的唯一参考号。
- 如果可用,执行进程的用户名。
- 与方法调用相关的任何适当数据,前提是它不是敏感数据。方法参数值可以极大地帮助调试问题。
安全异常
安全异常通常与权限异常以相同的方式实现,具体取决于环境或应用程序架构。
安全异常和权限异常之间的主要区别在于用户执行的操作的性质。尝试访问受限资源(如网络文件夹)时,通常会发生安全异常。尝试执行不允许的进程时,权限异常可能适用。
向执行操作的用户传达安全异常很重要,给出受限资源的清晰描述,如果可能,还描述如何获得适当的安全权限。
记录安全异常时,存储以下详细信息很重要:
- 执行操作的用户的唯一标识符。
- 尝试的操作和受限资源的清晰描述。
- 异常发生的精确日期和时间。
- 日志条目的唯一参考号。
- 用户执行的操作,如果可以执行一个以上的选项操作。例如,如果用户尝试访问受限资源,则应将此与异常日志一起记录。
权限异常
权限异常通常与“软”资源相关,例如业务流程、应用程序功能或报表,而不是网络资源或类似资源。
当用户的权限不足以访问某个资源时,向用户传达权限不足以及最低要求很重要。如果可能,还描述用户如何申请提升的权限。
记录权限异常与上面描述的记录安全异常非常相似。但是,在高容量环境中,将安全异常和权限异常记录在单独的日志中可能会很有帮助;否则,为调试或环境管理提供清晰的日志类型指示可能很有用。
业务异常
当用户尝试使用超出定义的业务规则的参数执行某个流程,或者当定义的业务流程未能端到端成功完成,导致流程处于不理想状态时,会发生业务异常。
最常见的业务异常示例是,例如,当用户尝试为一个不允许Orders
的Customer
创建Order
时,或者例如,一个流程需要创建多个文档,其中一个文档失败导致流程处于不平衡状态。
向用户显示异常时,清楚地描述失败的完整业务规则,或完成流程中失败的部分,以及如何响应异常非常重要。如果存在替代方案,必须向用户清晰地描述,或说明用户可以在何处找到这些替代方案。对于因异常导致流程处于不理想状态的情况,应尽可能描述纠正措施。
记录业务异常时,存储异常的每个详细信息至关重要。异常应与其他类型的异常分开存储,因为通常会有业务分析师或“超级用户”监控和响应业务异常。
在可以(也称为可恢复业务异常)通过干预继续的流程中,必须记录流程数据的全部详细信息以实现恢复。一个好的例子是为一个信用额度不足的客户开具发票,一旦客户的信用额度提高,就可以执行发票。如果用户不必重新输入整个发票,那将很有帮助。
如果流程处于不理想状态,记录足够纠正流程状态所需的信息非常重要。例如,如果无法为客户创建信用文件,导致客户余额不正确,则应存储客户ID和金额,以便系统管理员手动纠正情况,或客户经理与客户沟通。
除了上述数据之外,以下信息也应与业务异常一起记录:
- 执行流程的人员的用户名,如果可用。
- 失败评估的业务规则的名称,或流程的描述以及流程失败的部分。
- 如果可用,向用户推荐的纠正措施。
- 如果可用,用户响应异常通知所采取的选项。
- 异常日志的唯一参考号。
- 异常发生的精确日期和时间。
实现异常处理
异常处理通常在开发几乎完成时才实施,由于时间紧迫,只能实施最基本的(不足的)异常处理。
异常处理应作为应用程序设计的一部分,也是与客户进行审批和签署过程的一部分。
精心构建的异常处理和管理在很大程度上是可重用的,初步投资将在未来的实施中证明其价值。
有许多工具集和技术可用于协助异常处理,但重要的是要认识到异常处理对于应用程序的功能和质量至关重要。
确保你的异常得到妥善处理,将提高你的客户/用户体验和信心,改善应用程序的可维护性,提高稳定性和可用性,并使维护更容易,所有这些付出的努力并不多。
关注点
在后续的文章中,我将展示如何根据上述描述,使用自写的类和方法来简化异常处理。
历史
- 2009年1月19日:初次发布