当我们在try / catch中包装一堆语句并且其中一个发出异常时,在catch中我们无法知道哪个语句导致了异常(ex.stacktrace显示了我们当前的方法(doit) ),它的调用者,调用者的调用者等,但是do1或do2都没有:
function doit() {
try {
do1();
do2();
[...]
}
catch (Exception ex) {
// what failed?
}
}
一般来说,我已经采取了包装所有陈述和重新抛出,有点像:
private void do1() {
try {
// do whatever
} catch(Exception e) {
// write to my error log
throw new Exception("do1: " + e.Message, e.InnerException);
}
}
在我的日志中留下了一串痕迹,并使链条可用于上游。当然,问题是我必须使用这种代码包装我编写的每个方法。
某事告诉我,我对此感到愚蠢。什么是正确的方法?答案 0 :(得分:7)
好的,这很难做到正确,因为异常处理是一个非常敏感的话题,过去人们就如何正确地进行宗教战争。
首先:既不使用空捕获(try { ... } catch { ... }
),也不使用catch(Exception ex)
。
Exception派生类的唯一目的是为您提供有关发生的异常类型的丰富信息,以便您可以在异常处理程序中执行有意义的操作(如果线程崩溃重新启动它,如果数据库连接失败非永久性再次尝试,然后失败,等等。)
人们倾向于使用catch-all处理程序来处理其代码的最外层部分以记录未捕获的异常,这是好的,但无论如何,您应该提示用户或重新抛出异常(使用{{1} },而不是throw
- 关于此问题也有很多讨论。)
基本上,您没有以编程方式关注异常发生的地方 。 您可以处理它,也可以不处理它。如果你无法处理它,那你就不会抓住它。
附录:最重要的原因是“如果你可以做些什么,就这样做,否则你不敢触摸那个例外”的理念是无声地捕捉异常(无论是否记录)可以导致真正难以发现的错误。将它们推送到日志文件可能是不够的,因为在实时系统中,您可能无法获得完全注释的堆栈跟踪(包含行号和所有内容)。
附录2 :例如,需要整数输入的文本框。如果用户提供的字符串无法有效处理输入,则可能会抛出转换异常,捕获该特定异常并将文本框重置为其旧值,并可能通知用户错误输入。或者,您的程序可能只是因为异常而死(设计不好,您可以从该异常中恢复)或者静默地继续显示错误的输入,但仍然使用旧值(设计错误,程序误导)。
答案 1 :(得分:4)
如果您真的在这样做时出售(其他人已经说明了为什么这已经很糟糕),请使用面向方面编程方法。这将使您的生活变得更加轻松,并减少您最终编写和维护的代码量。
看看PostSharp,它为您提供了一个框架,允许您使用将为您生成此样板错误处理的属性修饰方法,类或命名空间。
答案 2 :(得分:3)
@Branko在评论中将其钉住:异常的堆栈跟踪显示抛出异常的点,而不是它被捕获的位置。
+1 @ ChaosPandion的评论:这是一个非常非常糟糕的主意。
答案 3 :(得分:1)
try
和catch
可能是设计最差的现代编程机制。我们不再有能力处理错误;如果发生单个异常,我们必须使整个过程失败,除非我们做一些可怕的事情,比如单独尝试每个语句。不再有恢复的选择,只能尽可能优雅地失败。
到目前为止,我发现的最佳模式是将每个用户事件包装在try
/ catch
中(使用方法,而不是每次都进行显式尝试)。例如:
public static class Defines
{
public static bool TryAction(Action pAction)
{
try { pAction(); return true; }
catch(Exception exception) { PostException(exception); return false; }
}
}
...
private void DoSomething(int pValue)
{
...
}
private void MyControl_MyEvent(object pSender, MyEventArgs pEventArgs)
{
Defines.TryAction(() => DoSomething(pEventArgs.Data));
}
除此之外,只需尝试编写无异常代码。只有在您很可能遇到异常时才使用显式try
,并且想要做的不仅仅是优雅地失败。