在C#中使用Exception类

时间:2020-03-06 14:29:47  来源:igfitidea点击:

在数据访问层的深处甚至更高的层中发生的错误(例如在ADO.net操作中)对于最终用户来说几乎没有多大意义。简单地将这些错误冒泡到UI并显示它们通常只会使最终用户感到沮丧。

最近,我采用了一种基本技术来报告错误,例如这样,我就可以捕获错误并至少添加一些用户友好的文本,以便至少最终用户可以了解失败的原因。

为此,我要在每个特定函数(例如,数据访问层中的提取函数)中捕获异常,然后使用用户友好的文本引发有关该函数失败并可能导致的错误,然后嵌入原始错误新异常中的异常作为该新异常的"内部异常"。

然后,如果需要,可以在每一层上发生这种情况,低级函数的每个使用者都将其自己的上下文添加到错误消息中,以便到达UI的是一条越来越用户友好的错误消息。

一旦错误到达用户界面(如果有必要),它就可以遍历嵌套的异常,以显示错误消息,该错误消息首先告诉用户哪个操作失败了,还提供了一些有关实际出了什么问题的技术信息。

例如

"The list of customer names your
  requested could not be displayed."
  
  "Obtaining the list of customers
  you requested failed due to an error with the database."
  
  "There was an error connecting to the
  database when retrieving a list of
  customers"
  
  "Login failed for user xx"

我的问题是:这效率极低吗(所有嵌套的异常)?我怀疑这不是最佳实践,因此我应该怎么做才能实现同一目标,或者实际上我应该试图实现更好的目标?

解决方案

当然,这是非常低效的。但是在发生异常的情况下,该异常对于终端用户来说足够重要,因此我们不应该在意这一点。

通常,当我们处理异常时,性能和效率是我们最少的担心。我们应该更担心要采取一些措施来帮助用户从问题中恢复过来。如果在将特定记录写入数据库时​​遇到问题,请回滚更改或者至少转储行信息,以使用户不会丢失它。

是的,异常的代价很高,因此捕获和重新抛出或者引发更有用的异常会涉及一定的成本。

但是请稍等!如果我们正在使用的框架代码或者库引发异常,则说明事情已经发生了。我们是否对异常发生后多长时间传播一次错误消息有非功能性要求?我对此表示怀疑。真的重要吗?发生了一些不可预见的"异常"事件。最主要的是向用户提供明智,有用的信息。

我认为我们在做什么方面是正确的。

这只是有点可怕。

如果我们向最终用户显示错误,则该用户应该能够对此采取行动。在"无法显示我们请求的客户名称列表"中。情况下,用户只会想到"那又如何?"在所有这些情况下,只需显示"发生了不好的事情"消息即可。我们甚至不需要捕获这些异常,当出现问题时,让某些全局方法(例如application_error)处理它并显示一条通用消息。当我们或者用户可以对错误采取措施时,请抓住该错误并采取措施或者通知该用户。

但是,我们将希望记录未处理的每个错误。

顺便说一下,显示有关发生的错误的信息可能会导致安全漏洞。攻击者对系统了解得越少,他们找到系统的可能性就越小(请记住" sql语句中的语法错误:Select * From Users,其中username ='a'; drp数据库;-。"之类的消息。 ":" drop"而不是" drp"。它们不再创建类似此类的网站)。

在我工作的地方,只有几个原因可以捕获异常。我们只有在...时才这样做

  • 我们可以为此做些事情-例如,我们知道有时可能会发生这种情况,并且可以在代码发生时对其进行纠正(非常少见)。
  • 我们想知道它发生的时间和位置(然后我们将异常重新抛出)。
  • 我们想要添加一个友好的消息,在这种情况下,我们将原始异常包装在从应用程序异常派生的新异常中,并向其中添加友好的消息,然后使其从那时起一直冒泡。

在示例中,我们可能仅显示"发生登录错误"。并在记录实际错误的同时保留了该错误,并为用户提供了为什么要深入研究异常(如果他们愿意)的原因。 (可能是错误表单上的一个按钮)。

  • 我们希望完全抑制异常并继续进行。不用说,我们仅对预期的异常类型执行此操作,并且仅在没有其他方法可以检测到生成异常的条件时才这样做。

抛出新的异常从技术上讲代价很高,但是我不会对此进行大辩论,因为如果我们每分钟抛出100个这样的异常,"代价高昂"是相对的,那么我们可能看不到成本。如果我们每秒抛出1000个此类异常,则很可能会遇到性能下降的情况(因此,在这里不值得讨论性能是要求)。

我想我不得不问为什么要使用这种方法。确实可以在可能引发异常的每个级别添加有意义的异常信息,如果是这样,那么该信息也将是:

  • 我们实际上想与用户分享什么?
  • 用户将能够解释,理解和使用某些东西?
  • 以这样的方式编写:它不会干扰以后对低级组件的重用,而这些低级组件的实用程序在编写时可能不知道?

我询问与用户共享信息的原因,因为在示例中,人工堆栈首先通知用户在数据库上进行身份验证存在问题。对于潜在的黑客来说,这是一条很好的信息,它可以揭示有关该操作的内容。

至于交出整个自定义异常堆栈,我认为这对大多数(诚实的)用户都没有用。例如,如果我在获取客户名称列表时遇到麻烦,这是否可以帮助我(作为用户)知道使用数据库进行身份验证时遇到的问题?除非我们使用集成身份验证,否则每个用户都有一个帐户,并且能够与系统管理员联系以找出其帐户缺少特权的原因,也许不是。

首先,我要确定抛出的Framework异常与我们要提供给用户的异常消息之间是否确实存在语义上的区别。如果存在,请继续使用最低级别的自定义异常(示例中的"登录失败")。紧随其后的步骤,直到实际呈现异常为止,实际上并不需要任何自定义异常。我们感兴趣的异常已经生成(登录失败),继续将消息包装在调用堆栈的每个级别上并没有真正的目的,只不过是将调用堆栈暴露给用户。对于那些"中间"步骤,假设存在任何尝试/捕获块,那么简单的"日志和抛出"策略就可以很好地工作。

但是,实际上,这种策略还有另一个潜在的缺陷:它迫使开发人员承担维护已实施的自定义例外标准的责任。由于编写低级类型时我们可能无法知道调用层次结构的每个排列(它们的"客户端"甚至可能尚未编写),因此似乎所有开发人员甚至一个开发人员都不会记得包装和自定义任何错误每个代码块中的条件。

我通常不担心自下而上地工作,而是担心在过程中尽可能晚地显示抛出的异常(即,尽可能接近调用堆栈的"顶部")。通常,我不会尝试替换任何在我的应用程序低级引发的异常中的消息,特别是因为这些低级成员的使用往往会随着调用的深入而变得越来越抽象。我倾向于在业务层和更低层中捕获和记录异常,然后处理在表示层中以一种可理解的方式显示它们。

以下是一些有关异常处理最佳实践的不错的文章:

http://www.codeproject.com/KB/architecture/exceptionbestpractices.aspx

http://aspalliance.com/1119

哎呀,这真是罗word……提前道歉。