我应该只捕获异常来记录它们吗?
Posted
技术标签:
【中文标题】我应该只捕获异常来记录它们吗?【英文标题】:Should I catch exceptions only to log them? 【发布时间】:2010-09-10 20:48:18 【问题描述】:我应该捕获异常以记录日志吗?
公共 foo(..) 尝试 ... 捕捉(异常前) Logger.Error(ex); 扔;如果我在我的每一层(DataAccess、Business 和 WebService)中都设置了这个,这意味着异常被记录了多次。
如果我的层位于单独的项目中并且只有公共接口具有 try/catch,这样做是否有意义? 为什么?为什么不?我可以使用其他方法吗?
【问题讨论】:
【参考方案1】:一般的经验法则是,只有在您可以实际采取措施时才能捕获异常。因此,在业务或数据层,您只会在以下情况下捕获异常:
try
this.Persist(trans);
catch(Exception ex)
trans.Rollback();
throw ex;
我的业务/数据层尝试保存数据 - 如果生成异常,则回滚所有事务并将异常发送到 UI 层。
在 UI 层,你可以实现一个通用的异常处理程序:
Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
然后处理所有异常。它可能会记录异常,然后显示用户友好的响应:
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 - 而不是抛出异常。
【讨论】:
顺便说一句,不要通过throw ex
重新抛出。只需执行throw
否则您将丢失原始异常中的堆栈跟踪。【参考方案2】:
一般来说,这是不好的做法,除非您出于特定原因需要登录。
就一般的日志异常而言,应该在根异常处理程序中处理。
【讨论】:
【参考方案3】:有时您需要记录处理异常时不可用的数据。在这种情况下,记录只是为了获取该信息是合适的。
例如(Java 伪代码):
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
这类似于追溯日志(我的术语),您可以缓冲事件日志,但除非有触发事件(例如抛出异常),否则不要写入它们。
【讨论】:
【参考方案4】:This Software Engineering Radio 播客是错误处理最佳实践的一个很好的参考。实际上有2个讲座。
【讨论】:
【参考方案5】:您需要制定处理异常的策略。我不推荐接球再扔。除了多余的日志条目之外,它还使代码更难阅读。 考虑将异常写入构造函数中的日志。这为您想要恢复的异常保留了 try/catch;使代码更易于阅读。要处理意外或不可恢复的异常,您可能希望在程序最外层附近使用 try/catch 来记录诊断信息。
顺便说一句,如果这是 C++,您的 catch 块正在创建异常对象的副本,这可能是其他问题的潜在来源。尝试捕获对异常类型的引用:
catch (const Exception& ex) ...【讨论】:
【参考方案6】:您将希望在层边界处登录。例如,如果您的业务层可以部署在 n 层应用程序中物理上独立的机器上,那么以这种方式记录和抛出错误是有意义的。
通过这种方式,您可以在服务器上记录异常日志,而无需在客户端机器上四处寻找发生了什么。
我在使用 Remoting 或 ASMX Web 服务的应用程序的业务层中使用此模式。使用 WCF,您可以使用附加到 ChannelDispatcher(完全是另一个主题)的 IErrorHandler 拦截并记录异常 - 因此您不需要 try/catch/throw 模式。
【讨论】:
【参考方案7】:翻译异常是一种很好的做法。不要只记录它们。如果你想知道抛出异常的具体原因,抛出具体的异常:
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);
【讨论】:
他,这不是c#的问题吗?【参考方案8】:这取决于异常:如果这实际上不应该发生,我肯定会记录它。另一方面:如果您期望这个异常,您应该考虑应用程序的设计。
无论哪种方式:您至少应该尝试指定要重新抛出、捕获或记录的异常。
public foo(..)
try
...
catch (NullReferenceException ex)
DoSmth(e);
catch (ArgumentExcetion ex)
DoSmth(e);
catch (Exception ex)
DoSmth(e);
【讨论】:
【参考方案9】:我的方法是只在处理程序中记录异常。可以说是“真正的”处理程序。否则,日志将很难阅读,代码的结构也很差。
【讨论】:
【参考方案10】:您可能想查找标准异常处理样式,但我的理解是:在您可以向异常添加额外细节的级别处理异常,或者在您将向用户呈现异常的级别处理异常。
在您的示例中,您除了捕获异常、记录它并再次抛出它之外什么都不做。如果您所做的只是记录,为什么不使用一次 try/catch 在***别捕获它,而不是在每个方法中捕获它是吗?
如果您要在再次抛出异常之前向异常添加一些有用的信息,我只会在该层处理它 - 将异常包装在您创建的新异常中,该异常具有超出低级异常文本的有用信息,这通常意味着对没有背景的人来说几乎没有..
【讨论】:
【参考方案11】:如果这是它唯一做的事情,我认为最好从这些类中删除 try/catch,并将异常引发到负责处理它们的类。这样一来,每个异常您只会获得一个日志,从而为您提供更清晰的日志,甚至可以记录堆栈跟踪,这样您就不会错过异常的起源。
【讨论】:
我不是线程制造者,但是您如何让异常引发?只是通过写 throw 或其他什么?【参考方案12】:您可能希望在***别登录,这通常是您的 UI 或 Web 服务代码。多次记录是一种浪费。此外,您想在查看日志时了解整个故事。
在我们的一个应用程序中,我们所有的页面都派生自一个 BasePage 对象,该对象负责异常处理和错误日志记录。
【讨论】:
【参考方案13】:如果您需要记录所有异常,那么这是一个绝妙的主意。也就是说,在没有其他原因的情况下记录所有异常并不是一个好主意。
【讨论】:
【参考方案14】:使用您自己的异常来包装内置异常。这样,您可以在捕获异常时区分已知错误和未知错误。如果您有一个方法调用其他可能引发异常以对预期和意外失败做出反应的方法,这将非常有用
【讨论】:
【参考方案15】:除非您要更改异常,否则您应该只在您要处理错误的级别记录,而不是重新抛出它。否则,您的日志只会有一堆“噪音”,每层记录 3 条或更多相同的消息。
我的最佳做法是:
-
仅在公共方法中尝试/捕获(一般而言;显然,如果您要捕获特定错误,您会在那里检查)
仅在抑制错误并重定向到错误页面/表单之前登录 UI 层。
【讨论】:
【参考方案16】:绝对不是。您应该找到处理异常的正确位置(实际上是做一些事情,例如 catch-and-not-rethrow),然后记录它。当然,您可以并且应该包含整个堆栈跟踪,但是按照您的建议会在代码中乱扔 try-catch 块。
【讨论】:
以上是关于我应该只捕获异常来记录它们吗?的主要内容,如果未能解决你的问题,请参考以下文章