什么应该是最好的异常处理策略

时间:2010-07-07 10:09:20

标签: c# design-patterns

我正在开发用户从UI调用方法的应用程序,我正在调用一个调用另一个方法的业务类的方法

UI - >方法1 - >方法2 - >方法3

如果在任何方法中发生任何异常,我想向用户显示错误消息。

我应该将异常直接抛到调用方法中,在UI层我将捕获异常并显示消息。

除了抛出异常并在调用者处捕获它有没有更好的方法来处理它?

我不想使用返回整数作为结果的C ++约定。

7 个答案:

答案 0 :(得分:19)

如果在发生异常的方法中无法从异常中恢复,请不要尝试捕获它(除非您想记录它,在这种情况下,在记录后再次抛出它) )。然后赶上UI级别。

尝试在每个级别捕获异常只会给代码增加膨胀。

答案 1 :(得分:3)

给出你的UI示例 - >方法1 - >方法2 - >方法3,我会确保UI有一个try / catch,但是其他方法都不应该捕获异常,除非它们可以在不重新抛出的情况下处理它们。即使他们处理异常,您也应该首先询问该异常是否应该发生。如果您处理异常并以愉快的方式行事,则该异常是您正常流程的一部分,这是一个严重的代码味道。

以下是我的建议:

1)将您的异常处理代码放在所有UI事件中,然后将实际操作与其他方法相关联。不要在整个地方分散异常处理代码。它很笨重,使代码难以阅读。

protected void Button1_Click(object sender, EventArgs e) {
   try {
      DoSomething();
   }
   catch Exception e {
      HandleError(e);
   }
}

2)不要这样做。您将丢失堆栈跟踪,下一位维护代码的开发人员将追捕您。

try {
   DoSomething();
}
catch Exception e {
   throw e; // don't rethrow!!!
}

如果您不打算处理异常,请勿抓住。或者,像这样使用裸体投掷:

try {
   DoSomething();
}
catch SomeException e {
   HandleException(e);
}
catch {
   throw ; // keep my stack trace.
}

3)仅在特殊情况下抛出异常。不要将try / catch块作为正常流程的一部分。

4)如果您要抛出异常,请不要永远抛出System.Exception。派生一个异常对象并抛出它。我经常只是一个通用的BusinessException类。这样,代码的用户可以确定您构成的异常以及与系统/环境相关的异常。

5)如果方法调用者违反了您方法的合同(前置条件),则抛出ArgumentExceptionArgumentNullExceptionArgumentOutOfRangeException。如果你抓住其中一个,这是一个编程错误。用户永远不应该看到这些例外。

如果您记得异常应该是非常罕见的,并且处理它们几乎总是一个UI问题(所以你的try / catch代码的大部分应该保持在UI级别)你会做得很好。

答案 2 :(得分:2)

基本规则是“当你无法处理时,不要捕获异常。”因此任何意外的异常都应该最终告诉用户出现了问题,并尽可能优雅地关闭应用程序。不关闭应用程序是有风险的,因为状态可能已损坏,反过来又损坏了对用户重要的信息。

强大的应用程序是模块化的,因此应用程序可以在不关闭的情况下通过停止或重新启动服务或插件等单个组件来恢复。

在某些情况下,您可能希望捕获无法处理的异常,但应该重新抛出异常或抛出新的异常。

如果您想记录异常,您将捕获,记录并重新抛出。

如果你想改进你将捕获的错误消息,请包装一个新的异常,然后抛出。例如,您可能希望在读取新异常中的文件时包含通用FileNotFoundException,并使用更具描述性的消息告诉用户无法找到哪个文件。

答案 3 :(得分:2)

无论你决定什么 - 保持一致。从现在起30天(或其他开发人员)需要了解您的代码。

此外,正如Scott Hanselman喜欢引用他的播客(我认为它可能出现在美丽的代码书中) -

  

计算机科学中的所有问题都可以通过另一层次的间接解决,“这是一个着名的引用归功于巴特勒兰普森,这位1972年设想现代个人计算机的科学家。

抛出异常是很昂贵的。我喜欢让我的业务层有自己的专门异常,只有它才能抛出。这样就可以更容易地追踪它。例如(因为我面前没有C#编译器,因此我不知道):

public class MyException : Exception
{
   private MyException(Exception ex, string msg) {
       this.Message = msg;
       this.InnerException = ex;
   }

   internal static MyException GetSomethingBadHappened(Exception ex, string someInfo) {
      return new MyException(ex, someInfo);
   }
}

答案 4 :(得分:1)

根据经验,例外情况仅应用于例外情况。也就是说,如果您希望方法调用有时失败,则不应使用异常。如果有几种可能的结果,您可以使用枚举:

public enum AddCustomerResult
{
    Success,
    CustomerAlreadyExists,
    CustomerPreviouslyRetired
}

对于数据库不可用的错误等,您仍然会抛出异常;但是你要测试预期的(虽然可能是罕见的)情况并指出成功/失败/等等。

这只是一种适合我的技术。

除非您想要在特殊情况下以及存在图层遍历(我自己的约定,不知道它是多么正确)的任何地方抛出它们,否则您希望捕获异常和使用该图层的自定义包装类重新抛出。

例如,在DAL中,您可能希望捕获异常并将它们重新抛出为DataAccessException的内部异常;在Web服务上,您将所有方法包装在异常处理程序中,以重新抛出MyWebServiceException。在UI(从应用程序内部遍历用户的屏幕)你想要捕获,记录并给他们一个很好的消息。据我所知,没有理由在任何其他地方捕捉或重新抛出。

它使您有机会隐藏基础异常(您可能不希望向调用方公开:例如数据库错误),集中登录,并提供常见的可重复故障模式。

在您的示例中,您只能在UI级别捕获异常;因为如果UI操作失败,您不希望应用程序因未处理的异常而崩溃。

希望有所帮助!

答案 5 :(得分:1)

当您设计多层应用程序时,您应该记住,这些层可以是分布式的,可能托管在不同机器上的不同服务中,因此必须通过远程边界抛出异常,这不是一种非常可取的方式去做。 在这种情况下,在我看来,捕捉它们只是冒出某种错误信息或代码是一种更好的方法。另外,当你抓住它时,你应该总是跟踪异常。了解应用程序中的最新情况总是很好。

答案 6 :(得分:0)

分离异常处理和错误报告几乎总是有用。

抓住异常,捕获它们是有意义的,在那里你有足够的上下文来知道究竟发生了什么以及如何恢复 - 在Method1..3中。在捕获已知异常时,将记录推送到错误日志。

完成操作或步骤后,UI级别可以检查错误日志并显示“发生以下错误:...”的消息。