什么应该是最好的exception处理策略
我正在开发用户从UI调用方法的应用程序,我正在调用一个调用另一个方法的业务类的方法
UI – > Method1 – > Method2 – > Method3
如果在任何方法中发生任何exception,我想向用户显示错误消息。
我应该将exception直接抛到调用方法,在UI层我将捕获exception并显示消息。
除了抛出exception并在调用者处捕获它有没有更好的方法来处理它?
我不想使用C ++约定,其中返回整数作为结果。
给出你的UI – > Method1 – > Method2 – > Method3的例子,我会确保UI有一个try / catch,但是其他方法都不应该捕获exception,除非他们可以处理它们而不重新抛出。 即使他们处理exception,你也应该质疑是否应该首先发生exception。 如果您处理exception并以愉快的方式行事,那么该exception是您正常流程的一部分,这是一个严重的代码味道。
以下是我的建议:
1)将您的exception处理代码放在所有UI事件中,然后将实际操作与其他方法相关联。 不要在整个地方分散exception处理代码。 它很笨重,使代码难以阅读。
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!!! }
如果您不打算处理exception, 请不要抓住它 。 或者,像这样使用裸体投掷:
try { DoSomething(); } catch SomeException e { HandleException(e); } catch { throw ; // keep my stack trace. }
3)仅在特殊情况下抛出exception。 不要将try / catch块作为正常流程的一部分。
4)如果你要抛出exception,不要抛出System.Exception。 派生一个exception对象并抛出它。 我经常只是一个通用的BusinessException
类。 这样,代码的用户可以确定您编写的exception以及与系统/环境相关的exception。
5)如果方法调用者违反了方法的约定(前置条件),则抛出ArgumentException
, ArgumentNullException
或ArgumentOutOfRangeException
。 如果你发现其中一个,这是一个编程错误。 用户永远不应该看到这些exception。
如果你记得exception应该是非常罕见的,并且处理它们几乎总是一个UI问题(所以你的try / catch代码的大部分应该保持在UI级别)你会做得很好。
如果无法从发生exception的方法中的exception中恢复 ,请不要尝试捕获它(除非您想记录它,在这种情况下,在记录后再次抛出它)。 然后赶上UI级别。
尝试在每个级别捕获exception只会给代码增加膨胀。
基本规则是“当你无法处理它时,不要捕获exception”。 因此,任何意外的exception都应该最终告诉用户出现问题并尽可能优雅地关闭应用程序。 不关闭应用程序是有风险的,因为状态可能已损坏,反过来又损坏了对用户重要的信息。
强大的应用程序是模块化的,因此应用程序可以通过停止或重新启动服务或插件等单个组件来关闭而无需关闭。
在某些情况下,您可能希望捕获无法处理的exception,但应该重新抛出exception或抛出新的exception。
如果要记录exception,您将捕获,记录并重新抛出。
如果你想改进你将捕获的错误消息,包装一个新的exception,并抛出。 例如,您可能希望在读取新exception中的文件时包装通用FileNotFoundException,并使用更具描述性的消息告知用户无法找到哪个文件。
无论你决定什么 – 保持一致 。 从现在起30天(或其他开发人员)将需要了解您的代码。
此外,正如斯科特汉塞尔曼喜欢引用他的播客(我认为它可能在美丽的代码书中) –
计算机科学中的所有问题都可以通过另一层次的间接解决,“这是一个着名的引用归功于巴特勒兰普森,这位1972年设想现代个人计算机的科学家。
抛出exception是昂贵的。 我喜欢让我的业务层有自己的专门exception,只有它才能抛出。 这样就可以更容易地追踪它。 例如(因为我没有在我面前的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); } }
根据经验,例外情况仅应用于例外情况。 也就是说,如果您希望方法调用有时失败,则不应使用exception。 如果有几种可能的结果,您可以使用枚举:
public enum AddCustomerResult { Success, CustomerAlreadyExists, CustomerPreviouslyRetired }
对于数据库不可用的错误等,您仍然会抛出exception; 但是你要测试预期的(虽然可能是罕见的)情况,并根据需要指出成功/失败/等。
这只是一种适合我的技术。
如果出现exception,您希望在特殊情况下抛出它们以及存在层遍历的任何地方(我自己的约定,不知道它是多么正确),您希望捕获exception并重新抛出该层的自定义包装类。
例如,在DAL中,您可能希望捕获exception并将它们重新抛出为DataAccessException的内部exception; 在Web服务上,您将所有方法包装在exception处理程序中,以重新抛出MyWebServiceException。 在UI(从应用程序内部遍历用户的屏幕)你想要捕获,记录并给他们一个很好的消息。 据我所知,没有理由在任何其他地方捕捉或重新抛出。
它使您有机会隐藏基础exception(您可能不希望向调用者公开:例如数据库错误),集中登录,并提供常见的可重复故障模式。
在您的示例中,您只能在UI级别捕获exception; 因为如果UI操作失败,您不希望应用程序因未处理的exception而崩溃。
希望有所帮助!
在设计多层应用程序时,您应该记住,这些层可以是分布式的,可能托管在不同机器上的不同服务中,因此必须通过远程边界抛出exception,这不是一种非常好的方法。 在这种情况下,在我看来,捕获它们只是冒出某种错误信息或代码是一种更好的方法。 另外,当你抓住它时,你应该总是跟踪exception。 了解应用程序中的最新情况总是很好。
分离exception处理和错误报告几乎总是有用。
捕获exception,捕获它们是有意义的,在那里你有足够的上下文来知道究竟发生了什么以及如何恢复 – 在Method1..3中。 在捕获已知exception时,将记录推送到错误日志。
完成操作或步骤UI级别后,可以检查错误日志并显示“发生以下错误:…”的消息。