n层应用程序中的异常处理?

时间:2010-11-06 13:29:53

标签: c# exception-handling n-tier-architecture

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

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

考虑一个简单的例子。假设您有一个调用业务层的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);
    }
}

您对上述异常处理有何批评?

谢谢

5 个答案:

答案 0 :(得分:19)

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

然而,可能有一些理由在其他层中捕获异常:

  1. 实际处理了异常。例如。连接失败,但层重新尝试。
  2. 使用更多信息重新抛出异常(查看堆栈时尚未提供)。例如。 DAL可能会报告它尝试连接的数据库,SqlException不会告诉您。
  3. 异常被转换为更一般的异常,它是该层接口的一部分,可能(或可能不)处理得更高。例如。 DAL可能会捕获连接错误并抛出DatabaseUnavailableException。对于不重要的操作,BL可能会忽略它,或者可能让它为那些操作传播。如果BL捕获SqlException而不是它将暴露给DAL的实现细节。相反,抛出DatabaseUnavailableException的可能性是DAL界面的一部分。
  4. 在多个层中记录相同的错误通常没有用,但我可以想到一个例外:当较低层不知道问题是否严重时,它可以将其记录为警告。如果更高层确定 关键,则可以将相同的问题记录为错误。

答案 1 :(得分:11)

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

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

答案 2 :(得分:3)

要解决的首要问题是永远不要抛出一般Exception

第二,除非真的有理由包装异常,否则只需在catch子句中使用throw;而不是throw new...

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

答案 3 :(得分:2)

任何应用程序都难以处理异常。您需要考虑每个例外,并快速进入适合您的模式。我尝试将例外分组为以下类别之一...

  • 只有在代码错误(或您不理解,或者您无法控制代码)时才会出现例外情况:

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

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

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

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

  • 实际由用户引起的例外:

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

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

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

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

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

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

答案 4 :(得分:0)

我每个层都有一个异常的异常类(DALException,BLException,...)来记录(例如:在文件中)层边界的异常(这是针对管理员的),因为用户只应该看清楚且易懂错误信息。这些异常应该处理所有数据访问层calsses所有层继承的DAlBase。我们可以在几个类中集中异常处理,开发人员只会抛出layerexception(例如:DALException) 查看更多信息MultiTier Exception Handling