n层应用程序中的exception处理?

在分层应用程序中处理exception的建议方法或最佳实践是什么?

  • 你应该在哪里放置try/catch块?
  • 你应该在哪里实施记录?
  • 是否有建议的模式来管理n层应用程序中的exception?

考虑一个简单的例子。 假设您有一个调用业务层的UI,它调用数据层:

 //UI protected void ButtonClick_GetObject(object sender, EventArgs e) { try { MyObj obj = Business.GetObj(); } catch (Exception ex) { Logger.Log(ex); //should the logging happen here, or at source? MessageBox.Show("An error occurred"); } } //Business public MyObj GetObj() { //is this try/catch block redundant? try { MyObj obj = DAL.GetObj(); } catch (Exception ex) { throw new Exception("A DAL Exception occurred", ex); } } //DAL public MyObj GetObj() { //Or is this try/catch block redundant? try { //connect to database, get object } catch (SqlException ex) { throw new Exception("A SQLException occurred", ex); } } 

您对上述exception处理有何批评?

谢谢

我的经验法则通常是在顶层捕获exception并在那里记录(或以其他方式报告),因为这是您获得有关错误的最多信息的地方 – 最重要的是完整堆栈跟踪。

但是,可能有一些原因可以捕获其他层中的exception:

  1. 实际处理了exception。 例如。 连接失败,但层重新尝试。
  2. 使用更多信息 (通过查看堆栈时尚未提供)重新抛出exception 。 例如。 DAL可能会报告它尝试连接的数据库, SqlException不会告诉您。
  3. 该exception被转换为更一般的exception该exception是该层的接口的一部分,并且可能(或可能不)被处理得更高。 例如。 DAL可能会捕获连接错误并抛出DatabaseUnavailableException 。 对于不重要的操作,BL可能会忽略它,或者可能让它为那些操作传播。 如果BL捕获了SqlException那么它将暴露给DAL的实现细节。 相反,抛出DatabaseUnavailableException的可能性是DAL接口的一部分。

在多个层中记录相同的错误通常没有用,但我可以想到一个例外:当较低层不知道问题是否严重时,它可以将其记录为警告。 如果更高层确定它关键的,那么它可以将相同的问题记录为错误。

以下是我遵循的与exception处理相关的一些规则:

  • 只应在可以实现处理它们的逻辑的层中捕获exception。 大多数情况发生在最上层。 根据经验,在层中实现catch之前,问问自己任何上层的逻辑是否以任何方式依赖于exception的存在。 例如,在业务层中,您可能具有以下规则:如果服务可用,则从外部服务加载数据,如果不从本地缓存加载数据。 如果调用该服务会抛出一个exption,您可以捕获并将其记录在BL级别,然后从缓存中返回数据。 在这种情况下,上层UI层不必采取任何操作。 但是,如果服务和缓存调用都失败,则exception必须转到UI级别,以便可以向用户显示错误消息。
  • 应该捕获应用程序内的所有exception,如果没有用于处理它们的特殊逻辑,则应至少记录它们。 当然,这并不意味着您必须包装try / catch块中所有方法的代码。 相反,任何应用程序类型都有未捕获exception的全局处理程序。 例如,在Windows应用Application.ThreadException中,应实现Application.ThreadException事件。 在ASP .Net应用程序中,应实现global.asax中的Application_Error事件处理程序。 这些位置是您可以在代码中捕获exception的最高位置。 在许多应用程序中,这将是您将捕获大多数exception的地方,除了日志记录之外,您还可以在此处实现一个通用且友好的错误消息窗口,该窗口将呈现给用户。
  • 您可以在需要的地方实现try / finallyfunction,而无需实现catch块。 如果您不需要实现任何exception处理逻辑,则不应实现catch块。 例如:
 SqlConnection conn = null; try { conn = new SqlConnection(connString); ... } // do not implement any catch in here. db exceptions logic should be implemented at upper levels finally { // finalization code that should always be executed. if(conn != null) conn.Dispose(); } 
  • 如果你需要从catch块中重新抛出exception,只需使用throw; 。 这将确保保留堆栈跟踪。 使用throw ex; 将重置堆栈跟踪。
  • 如果需要使用另一种对上层更有意义的类型重新抛出exception,请在新创建的exception的InnerException属性中包含原始exception。
  • 如果需要从代码中抛出exception,请使用有意义的exception类型。

要解决的第一件事就是永远不要抛出一般的Exception

第二个,除非有一个很好的理由来包装exception,否则就throw; 而不是在你的catch子句中throw new...

第三个(这不是硬性和快速的规则),不要在UI层下面的任何点捕获常规exception。 UI层应该捕获常规exception,以便可以向最终用户显示用户友好的消息,而不是爆炸的技术细节。 如果你在层中更深层次地捕获一般的例外,那么它可能会被无意中吞下并造成难以追踪的错误。

在任何应用程序中都很难处理exception。 您需要考虑每个exception并快速进入适合您的模式。 我尝试将exception分组到以下类别之一……

  • 只有在您的代码错误(或您不理解,或者您无法控制它们)时才会出现exception:

示例:可能您的持久性框架要求您捕获可能由格式错误的SQL引起的SQLexception,但是,您正在执行硬编码查询。

处理:根据我的经验,大多数例外属于此类别。 至少,记录它们。 更好的是,将它们发送到记录它们的exception处理服务。 然后在将来如果您决定以不同方式记录它们或使用它们做不同的事情,您可以在一个地方进行更改。 也许你还想向UI层发送一个标志,说明发生了某种错误,他们应该重试他们的操作。 也许你邮寄管理员。

您还需要将某些内容返回到更高层,以便服务器上的生命继续。 也许这是一个默认值,或者这可能是null。 也许你有办法取消整个操作。

另一种选择是给exception处理服务两种处理方法。 handleUnexpectedException()方法会通知用户但不会重新抛出exception,如果您有能力自行展开堆栈或以某种方式继续,则可以返回默认值。 handleFatalException()方法会通知用户并重新抛出某种exception,以便您可以让exception为您展开堆栈。

  • 实际由用户引起的exception:

示例:用户正在尝试更新foobar小部件并为其指定新名称,但已存在具有所需名称的foobar小部件。

处理:在这种情况下,您需要将exception返回给用户。 在这种情况下,你可以继续抛出exception(或者更好,甚至不要抓住它),直到它到达UI层,然后UI层应该知道如何处理exception。 确保记录这些exception,以便您(或任何人编写您的UI)知道它们存在并且知道期望它们。

  • 您可以实际处理的例外情况:

示例:您进行远程服务呼叫并且远程服务超时但您知道他们有这样做的历史记录,您应该只是重做呼叫。

处理:有时这些例外在第一类中开始。 在您的应用程序处于狂野状态一段时间之后,您会意识到您确实有一个很好的方法来处理它。 有时,与乐观锁定或中断exception的exception一样,捕获exception并对其执行某些操作只是业务的一部分。 在这些情况下,处理exception。 如果你是语言(我在想Java)区分已检查和未经检查的exception,我建议这些exception。

为了解决您的上述问题,我会将初始exception委托给一个服务,该服务将通知用户并根据MyObj的对象类型(例如设置)我可能会将此作为非致命exception并返回默认值或者如果我不能这样做(例如用户帐户)然后我可能会让这成为一个致命的例外,所以我不必担心它。

我在每个层(DALException,BLException,…)中使用sepearateexception类来记录(例如:在文件中)层边界的exception(这是管理员),因为用户只能看到清晰易懂的错误消息。 这些exception应该处理所有数据访问层calsses所有层inheritance的DAlBase。 我们可以将exception处理集中在少数类中,开发人员只会抛出layerexception(例如:DALException),请参阅MultiTierexception处理 。