5

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

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

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

如有必要,这可以在每一层发生,每个低层函数的消费者都将它自己的上下文添加到错误消息中,这样到达 UI 的就是越来越用户友好的错误消息。

一旦错误到达 UI - 如果有必要 - 它可以遍历嵌套的异常,以显示错误消息,首先告诉用户哪个操作失败,但也提供一些关于实际错误的技术信息。

例如

“您请求的客户名称列表无法显示。”

“由于数据库错误,获取您请求的客户列表失败。”

“检索客户列表时连接到数据库时出错”

“用户 xx 登录失败”

我的问题是:这是否非常低效(所有这些嵌套异常)?我怀疑这不是最佳实践,所以我应该怎么做才能实现同样的目标 - 或者我实际上应该尝试实现更好的目标?

4

6 回答 6

9

这只是有点可怕。

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

但是你会想要记录你没有处理的每一个错误。

顺便说一句,显示有关发生的错误的信息可能会导致安全漏洞。攻击者对您的系统了解得越少,他们就越不可能找到破解它的方法(请记住那些消息,例如“sql 语句中的语法错误:Select * From Users Where username='a'; drp database;--'.. ." 预期:'drop' 而不是 'drp'。他们不再制作这样的网站)。

于 2008-09-20T23:50:06.030 回答
8

抛出新异常在技术上代价高昂,但是我不会对此进行大辩论,因为“代价高昂”是相对的——如果你每分钟抛出 100 个这样的异常,你可能看不到成本;如果你每秒抛出 1000 个这样的异常,你很可能会看到性能下降(因此,这里不值得讨论 - 性能是你的决定)。

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

  • 您真正与用户分享的东西?
  • 您的用户将能够解释、理解和使用的东西?
  • 以不会干扰以后重用低级组件的方式编写,在编写时可能不知道它们的实用性?

我询问与您的用户共享信息的问题,因为在您的示例中,您的人工堆栈首先通知用户在数据库上进行身份验证时出现问题。对于潜在的黑客来说,这是一个很好的信息,可以揭示有关操作正在做什么的一些信息。

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

我将首先确定抛出的框架异常与您想提供给用户的异常消息之间是否真的存在语义差异。如果有,那么继续并在最低级别使用自定义异常(在您的示例中为“登录失败”)。接下来的步骤,直到异常的实际呈现,实际上并不需要任何自定义异常。您感兴趣的异常已经生成(登录失败) - 继续在调用堆栈的每个级别包装该消息除了将调用堆栈暴露给用户之外没有任何实际目的。对于那些“中间”步骤,假设有任何 try/catch 块,一个简单的“记录并抛出”策略就可以正常工作。

但实际上,这种策略还有另一个潜在缺陷:它强制开发人员负责维护已实施的自定义异常标准。由于在编写低级类型时您不可能知道调用层次结构的每一个排列(它们的“客户端”甚至可能还没有被编写),所有开发人员——甚至一个开发人员——似乎都不太可能记得包装和自定义每个代码块中的任何错误情况。

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

这里有几篇关于异常处理最佳实践的不错的文章:

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

http://aspalliance.com/1119

天哪,这太罗嗦了……提前道歉。

于 2008-09-21T00:36:29.210 回答
3

是的,异常很昂贵,因此捕获和重新抛出或抛出更有用的异常需要付出一定的代价。

但是等一下!如果您正在使用的框架代码或库引发异常,那么事情已经无法解决了。您是否对异常后错误消息的传播速度有非功能性要求?我对此表示怀疑。真的很重要吗?发生了一些无法预料和“异常”的事情。主要是向用户提供明智、有用的信息。

我认为你正在做的事情是正确的。

于 2008-09-20T23:49:09.700 回答
2

当然,这是非常低效的。但是在发生足以向最终用户显示的重要异常时,您不应该关心这一点。

于 2008-09-20T23:45:07.213 回答
2

在我工作的地方,我们只有几个理由来捕获异常。我们只在...

  1. 我们可以做点什么——例如,我们知道这有时会发生,我们可以在代码中纠正它(非常罕见)。

  2. 我们想知道它发生在哪里(然后我们只是重新抛出异常)。

  3. 我们想添加一条友好消息,在这种情况下,我们将原始异常包装在从应用程序异常派生的新异常中,并向其中添加一条友好消息,然后让它从那时起保持不变。

在您的示例中,我们可能只显示“发生登录错误”。anbd 在记录真正的错误并为用户提供他们想要深入研究异常的原因时将其保留。(可能是错误表单上的一个按钮)。

  1. 我们想完全抑制异常并继续前进。不用说,我们只对预期的异常类型执行此操作,并且仅在没有其他方法来检测生成异常的条件时才这样做。
于 2008-09-21T00:24:30.597 回答
1

通常,当您处理异常时,性能和效率是您最不担心的问题。您应该更担心做一些事情来帮助用户从问题中恢复过来。如果将某条记录写入数据库时​​出现问题,要么回滚更改,要么至少转储行信息,这样用户就不会丢失它。

于 2008-09-20T23:47:37.190 回答