我应该只捕获异常来记录它们吗?
我是否应该出于记录目的而捕获异常?
public foo(..) { try { ... } catch (Exception ex) { Logger.Error(ex); throw; } }
如果我在每个图层(DataAccess,Business和WebService)中都安装了此控件,则意味着多次记录了该异常。
如果我的图层在单独的项目中并且只有公共接口在其中有try / catch,这样做是否有意义?
为什么?为什么不?我可以使用其他方法吗?
解决方案
当然不。我们应该找到正确的位置来处理异常(实际上是执行某些操作,例如不扔不接),然后将其记录下来。当然,我们可以并且应该包括整个堆栈跟踪,但是按照建议进行操作会使代码带有try-catch块。
除非我们要更改该异常,否则仅应在要处理该错误的级别登录,而不要将其重新抛出。否则,日志只有一堆"噪音",每条记录一次记录3条或者更多条相同的消息。
我的最佳做法是:
- 仅尝试/捕获公共方法(通常;通常,如果我们正在捕获特定错误,则应在此处进行检查)
- 仅在抑制错误并重定向到错误页面/表单之前,立即登录UI层。
使用我们自己的异常包装inbuild异常。这样,我们可以在捕获异常时区分已知错误和未知错误。如果我们有一个方法可以调用其他可能抛出异常的方法以对预期的和意外的故障做出反应,则此方法很有用
如果我们需要记录所有异常,那么这是一个绝妙的主意。也就是说,在没有其他原因的情况下记录所有异常并不是一个好主意。
我们可能想以最高级别登录,通常是UI或者Web服务代码。多次记录是一种浪费。另外,当我们查看日志时,我们想了解整个故事。
在我们的一个应用程序中,我们所有的页面都是从BasePage对象派生的,并且该对象处理异常处理和错误记录。
如果那是唯一的事情,我认为最好从这些类中删除try / catch,然后将异常引发给负责处理它们的类。这样,每个异常我们只会获得一个日志,从而为我们提供更清晰的日志,甚至我们都可以记录堆栈跟踪,因此我们不会错过异常发生的源头。
我们可能希望查找标准的异常处理样式,但是我的理解是:在可以为异常添加更多细节的级别处理异常,或者在向用户呈现异常的级别处理异常。
在示例中,我们什么也没有做,只是捕获异常,将其记录下来,然后再次抛出..为什么不只通过一次try / catch捕获最高级别的异常,而不是在所有方法中都记录一次异常呢?
如果我们要在异常上添加一些有用的信息,然后再将其抛出,我只会在该层上处理它,将异常包装在我们创建的新异常中,该异常具有除低级异常文本之外的有用信息,这通常对没有该标记的任何人都没有什么意义一些背景..
我的方法是仅在处理程序中记录异常。可以这么说,"真正的"处理程序。否则,日志将非常难以阅读,代码结构也将不完整。
这取决于异常:如果实际上不应该发生这种情况,我肯定会记录下来。另一方面,如果我们期望出现此异常,则应考虑应用程序的设计。
两种方式:我们至少应尝试指定要重新抛出,捕获或者记录的异常。
public foo(..) { try { ... } catch (NullReferenceException ex) { DoSmth(e); } catch (ArgumentExcetion ex) { DoSmth(e); } catch (Exception ex) { DoSmth(e); } }
良好的做法是翻译异常。不要只记录它们。如果我们想知道引发异常的特定原因,请引发特定的异常:
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); } }
我们将要登录到层边界。例如,如果业务层可以部署在n层应用程序中的物理上独立的计算机上,则以这种方式记录并引发错误是有意义的。
这样,我们就可以在服务器上记录异常日志,而无需四处查看客户端计算机以查找发生的情况。
我在使用Remoting或者ASMX Web服务的应用程序的业务层中使用此模式。使用WCF,我们可以使用添加到ChannelDispatcher的IErrorHandler(完全是另一个主题)来拦截和记录异常,因此我们不需要try / catch / throw模式。
一般的经验法则是,只有在确实可以对异常进行处理的情况下,我们才能捕获异常。因此,在业务或者数据层,我们只会在这种情况下捕获异常:
try { this.Persist(trans); } catch(Exception ex) { trans.Rollback(); throw ex; }
如果生成异常,则所有业务都会回滚并将异常发送到UI层,我的业务/数据层将尝试保存数据。
在UI层,我们可以实现通用的异常处理程序:
Application.ThreadException + =新的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而不是抛出异常。
我们需要制定一种处理异常的策略。我不建议抓住并重新抛出。除了多余的日志条目外,它还使代码难以阅读。
考虑将异常写入构造函数中的日志。这将为我们要从中恢复的异常保留try / catch;使代码更易于阅读。为了处理意外或者不可恢复的异常,我们可能希望在程序最外层附近尝试/捕获日志以记录诊断信息。
顺便说一句,如果这是C ++,则catch块正在创建异常对象的副本,这可能是其他问题的潜在来源。尝试捕获对异常类型的引用:
catch (const Exception& ex) { ... }
该软件工程电台播客是错误处理最佳实践的很好的参考。实际上有2个讲座。
有时我们需要记录处理异常的地方不可用的数据。在这种情况下,仅记录日志以获取该信息是适当的。
例如(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 } }
这类似于追溯日志记录(我的术语),在该日志记录中,我们缓冲事件日志,但是除非有触发事件(例如引发异常),否则不写事件日志。