关于c#:我应该仅捕获异常来记录它们吗?

关于c#:我应该仅捕获异常来记录它们吗?

Should I catch exceptions only to log them?

我是否应该出于记录目的捕获异常?

1
2
3
4
5
6
7
8
9
10
public foo(..)
{
   try
   {
     ...
   } catch (Exception ex) {
     Logger.Error(ex);
     throw;
   }
}

如果我在每个图层(DataAccess,Business和WebService)中都安装了此控件,则意味着多次记录了该异常。

如果我的图层位于单独的项目中,并且只有公共接口在其中有try / catch,这样做是否有意义?
为什么? 为什么不? 我可以使用其他方法吗?


当然不。您应该找到正确的位置来处理异常(实际上是执行某些操作,例如"不追捕"),然后将其记录下来。当然,您可以并且应该包括整个堆栈跟踪,但是按照您的建议进行操作会使代码带有try-catch块。


除非您要更改该异常,否则仅应在要处理该错误的级别登录,而不要将其重新抛出。否则,您的日志只有一堆"噪音",每条记录一次记录3条或更多条相同的消息。

我的最佳做法是:

  • 仅尝试/捕获公共方法(通常;通常,如果您正在捕获特定错误,则应在此处进行检查)
  • 仅在抑制错误并重定向到错误页面/表单之前,先登录UI层。

  • 一般的经验法则是,只有在确实可以对异常进行处理的情况下,您才能捕获异常。因此,在业务或数据层,您只会在这种情况下捕获异常:

    1
    2
    3
    4
    5
    6
    7
    8
    9
                try
                {
                    this.Persist(trans);
                }
                catch(Exception ex)
                {
                    trans.Rollback();
                    throw ex;
                }

    我的业务/数据层尝试保存数据-如果生成异常,则会回滚所有事务,并将异常发送到UI层。

    在UI层,您可以实现一个通用的异常处理程序:

    Application.ThreadException + =新的ThreadExceptionEventHandler(Application_ThreadException);

    然后处理所有异常。它可能会记录异常,然后显示用户友好的响应:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
        static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
        {
            LogException(e.Exception);
        }
        static void LogException(Exception ex)
        {
            YYYExceptionHandling.HandleException(ex,
                YYYExceptionHandling.ExceptionPolicyType.YYY_Policy,
                YYYExceptionHandling.ExceptionPriority.Medium,
               "An error has occurred, please contact Administrator");
        }

    在实际的UI代码中,如果您要执行其他操作(例如显示不同的友好消息或修改屏幕等),则可以捕获单个异常。

    另外,作为提醒,请始终尝试处理错误-例如除以0-而不是引发异常。


    良好的做法是翻译异常。不要只记录它们。如果您想知道引发异常的特定原因,请引发特定异常:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void connect() throws ConnectionException {
       try {
           File conf = new File("blabla");
           ...
       } catch (FileNotFoundException ex) {
           LOGGER.error("log message", ex);
           throw new ConnectionException("The configuration file was not found", ex);
       }
    }

    有时您需要记录在处理异常的地方不可用的数据。在这种情况下,仅记录日志以获取该信息是适当的。

    例如(Java伪代码):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public void methodWithDynamicallyGeneratedSQL() throws SQLException {
        String sql = ...; // Generate some SQL
        try {
            ... // Try running the query
        }
        catch (SQLException ex) {
            // Don't bother to log the stack trace, that will
            // be printed when the exception is handled for real
            logger.error(ex.toString()+"For SQL: '"+sql+"'");
            throw ex;  // Handle the exception long after the SQL is gone
        }
    }

    这类似于追溯日志记录(我的术语),在该日志记录中,您缓冲事件日志,但是除非有触发事件(例如引发异常),否则不写事件日志。


    您可能希望查找标准的异常处理样式,但是我的理解是:在可以为异常添加更多细节的级别处理异常,或者在向用户呈现异常的级别处理异常。

    在您的示例中,您什么也没有做,只是捕获异常,将其记录下来,然后再次抛出..为什么不只通过一次try / catch捕获最高级别的异常,而不是在所有方法中都记录一次异常,而不是在每个方法中进行捕获?

    如果要在再次抛出异常之前向异常添加一些有用的信息,我只会在该层进行处理-将异常包装在您创建的新异常中,该异常具有除低级异常文本之外的有用信息,通常对任何人都没有什么意义没有上下文。


    使用您自己的异常包装inbuild异常。这样,您可以在捕获异常时区分已知错误和未知错误。如果您有一个方法可以调用其他可能抛出异常的方法来对预期的和意外的故障做出反应,则此方法很有用


    该软件工程电台播客是错误处理最佳实践的很好的参考。实际上有2个讲座。


    您需要制定一种处理异常的策略。我不建议抓住并重新抛出。除了多余的日志条目外,它还使代码难以阅读。
    考虑将异常写入构造函数中的日志。这将为您要从中恢复的异常保留try / catch;使代码更易于阅读。要处理意外或不可恢复的异常,您可能希望在程序最外层附近尝试/捕获日志以记录诊断信息。

    顺便说一句,如果这是C ++,则您的catch块正在创建异常对象的副本,这可能是其他问题的潜在根源。尝试捕获对异常类型的引用:

    1
      catch (const Exception& ex) { ... }


    您将需要登录到层边界。例如,如果您的业务层可以部署在n层应用程序中的物理上独立的计算机上,则以这种方式记录并引发错误是有意义的。

    这样,您就可以在服务器上记录异常日志,而无需四处查看客户端计算机以查找发生的情况。

    我在使用Remoting或ASMX Web服务的应用程序的业务层中使用此模式。使用WCF,您可以使用附加到ChannelDispatcher的IErrorHandler(完全是另一个主题)来拦截和记录异常(因此完全不需要try / catch / throw模式)。


    这取决于异常:如果实际上不应该发生这种情况,我肯定会记录下来。另一方面:如果您期望出现此异常,则应考虑应用程序的设计。

    两种方式:您至少应尝试指定要重新抛出,捕获或记录的异常。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public foo(..)
    {
       try
       {
         ...
       }
       catch (NullReferenceException ex) {
         DoSmth(e);
       }
       catch (ArgumentExcetion ex) {
         DoSmth(e);
       }
       catch (Exception ex) {
         DoSmth(e);
       }
    }

    我的方法是仅在处理程序中记录异常。可以说是"真正的"处理程序。否则,日志将非常难以阅读,代码结构也将不完整。


    如果那是唯一的事情,我认为最好从这些类中删除try / catch,然后将异常引发给负责处理它们的类。这样,每个异常只获得一个日志,从而为您提供更清晰的日志,甚至您都可以记录堆栈跟踪,因此您不会错过异常发生的源头。


    您可能想以最高级别登录,通常是您的UI或Web服务代码。多次记录是一种浪费。另外,您想在查看日志时了解整个故事。

    在我们的一个应用程序中,我们所有的页面都是从BasePage对象派生的,并且该对象处理异常处理和错误记录。


    如果您需要记录所有异常,那么这是一个绝妙的主意。也就是说,在没有其他原因的情况下记录所有异常并不是一个好主意。


    推荐阅读