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

fle*_*esh 31 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

谢谢

EMP*_*EMP 20

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

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

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

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

  • 谢谢,这是一个非常有用的答案. (2认同)

Flo*_*scu 11

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

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


Age*_*191 3

首先要解决的问题是永远不要抛出将军Exception

第二,除非确实有充分的理由来包装异常,否则只需在 catch 子句中使用throw;代替即可。throw new...

第三(这不是一个硬性规定),不要在 UI 层以下的任何点捕获一般异常。UI 层应该捕获一般异常,以便可以向最终用户显示用户友好的消息,而不是发生爆炸的技术细节。如果您在更深的层中捕获一般异常,则它可能会无意中被吞没,并导致很难追踪的错误。