2010-02-05 121 views
15

我想知道什么是正确的方法是从一个方法传递到另一个异常。传递异常的正确方法是什么? (C#)

我正在开发一个项目,分为演示(网页),业务和逻辑层以及错误(例如SqlExceptions)需要传递到链中,以便在出现问题时通知Web层。

我已经看到了3点基本的方法:

try 
{ 
    //error code 
} 
catch (Exception ex) 
{ 
    throw ex; 
} 

(简单地重新抛出)

try 
{ 
    //error code 
} 
catch (Exception ex) 
{ 
    throw new MyCustomException(); 
} 

(引发自定义异常,这样的数据提供商的依赖性不传递)
然后简单地

//error code 

(不做任何事根本就是让自己的错误冒起来)

当然,在catch块中也会发生一些日志记录。

我更喜欢3号,而我的同事使用方法1,但我们都不能真正激发原因。

使用每种方法的优点/缺点是什么?有没有更好的方法我不知道?有没有被接受的最佳方式?

+17

您的第一个示例是C#的反模式,使用'throw;'来代替,否则您将堆栈跟踪重置为当前的throw点。只要使用'throw;'就会保持原始的堆栈跟踪。 – 2010-02-05 22:21:35

+1

双重复制:http://stackoverflow.com/questions/178456/what-is-the-proper-way-to-re-throw-an-exception-in-c和http://stackoverflow.com/questions/ 22623 /净投掷例外的最佳实践。 – 2010-02-05 22:32:20

+1

请注意,即使您使用catch更改了异常的类型(Exception ex),也可以保留堆栈跟踪{throw new MyCustomException(ex); }如果您在MyCustomException类中使用Exception类的提供构造函数(就像所有的.NET Exception类型一样)。 – dbemerlin 2010-02-05 22:52:47

回答

13

如果你什么都不做,你应该简单地让它走上一些人会处理它的地方。

您可以随时处理其中的一部分(如日志记录)并重新抛出它。您只需发送throw;即可重新投掷,而不必显式指定名称。

try 
{ 

} 
catch (Exception e) 
{ 
    throw; 
} 

的优势来处理它,你可以确保一些机构那里通知您,您有一个错误,你有一个不疑。

但是,在某些情况下,比如说第三方,您希望让用户处理它,并且在这种情况下,您应该让它继续冒泡。

+1

请记住,在上面的例子中,您将捕获像NullReferenceException这样的错误,因此,在throw point和try/catch之间的任何finally块都将被执行 - 即使已经明显检测到一个致命错误。那些最后的街区会做什么,节目处于这个未知的状态?谁知道? – 2010-02-05 22:40:22

+2

@Earwicker,你的观点很好,但最后的块不管。面对不好的情况,终极块必须写得很强大。我在这里更关心的是安全影响,而不是健壮性影响。健壮已经在窗外;服务器崩溃了。让我们不要帮助任何导致服务器崩溃的人。 – 2010-02-05 22:50:57

+0

@Eric - 为我自己说话我不知道我知道如何写一个finally块,所以它对任何bug都是强大的! :)因此需要异常过滤和FailFast。但是我有时需要隐藏信息。您不希望将详细信息返回给生产站点上的客户端。但是你可能仍然希望尽可能私自登录服务器。 – 2010-02-05 22:56:31

6

正确的方式重新扔在C#中的例外是象下面这样:

try 
{ 
    .... 
} 
catch (Exception e) 
{ 
    throw; 
} 

的细节请参见本thread

+0

对,否则你会失去你的调用堆栈,这会使调试变得痛苦。 – 2010-02-05 22:24:09

6

只能围绕您期望和可以处理的异常使用try/catch块。如果你捕捉到了一些你无法处理的东西,它就会失败处理预期错误的try/catch的目的。

捕捉大的异常很少是一个好主意。你第一次遇到OutOfMemoryException你真的能够优雅地处理它吗?大多数API的文档都记录了每种方法可能抛出的异常,并且这些应该是唯一可以处理的异常,只有当您可以优雅地处理它时才是如此。

如果您想要处理链条上的错误,让它自己冒泡,而不会捕捉并重新抛出它。唯一的例外是用于记录目的,但在每一步记录都会做大量的工作。最好只记录你的公共方法可能允许冒泡的异常,并让API的使用者决定如何处理它。

9

我想你应该有一个稍微不同的问题

如何指望其他组件来与我的模块中抛出的异常互动开始?

如果消费者能够很好地处理较低/数据层抛出的异常,那么很简单,什么也不做。上层能够处理例外情况,并且您只应执行维持您的状态所需的最小数量,然后重新投掷。

如果消费者无法处理低级别异常,而是需要更高级别的异常,那么可以创建一个他们可以处理的新异常类。但请确保将原始异常传递给内部异常。

throw new MyCustomException(msg, ex); 
0

通常你只捕获你应该有异常,可以处理,让正常方式进一步推广应用工作。如果你想有一些额外的错误日志记录,你会发现一个异常,做记录并使用“throw”重新抛出它。所以堆栈跟踪不会被修改。自定义异常通常是为了报告特定于应用程序的错误而创建的。

3

我见过(并持有)有关这方面的各种强烈意见。答案是我认为目前在C#中没有理想的方法。

有一点我觉得(以Java思想的方式)异常是方法二进制接口的一部分,与返回类型和参数类型一样多。但在C#中,它根本不是。这从事实上是清楚的,没有抛出规范系统。

换句话说,如果您希望采取只有您的异常类型应该从您的库方法中跳出来的态度,那么您的客户不会依赖于您的库的内部细节。但很少有图书馆会这么做。

官方C#团队的建议是捕获可能由方法抛出的每种特定类型,如果您认为可以处理它们。不要抓住任何你无法处理的东西。这意味着不需要在库边界处封装内部异常。

但是反过来说,这意味着您需要对给定方法可能抛出的内容进行完美的记录。现代应用程序依赖于第三方库的迅速发展。如果他们都试图捕获特定的异常类型,这些异常类型在将来的库版本组合中可能是不正确的,并且没有编译时检查,那么它就会有一个静态类型系统的嘲弄。

所以有人这样做:

try 
{ 
} 
catch (Exception x) 
{ 
    // log the message, the stack trace, whatever 
} 

的问题是,这种捕获所有的异常,包括那些从根本上表明存在严重的问题,比如一个空引用异常。这意味着该程序处于未知状态。检测到的时刻,它应该在它对用户的持久数据造成一些损坏(开始垃圾文件,数据库记录等)之前关闭。

这里隐藏的问题是try/finally。这是一个非常棒的语言功能 - 事实上它非常重要 - 但是如果一个足够严重的例外情况出现,那么它是否真的会导致最终的块运行?如果有错误发生,您是否真的希望证据被破坏?如果程序处于未知状态,那么任何重要的东西都可能被那些最终块破坏。

所以,你真正想要的是什么(更新为C#6!):

try 
{ 
    // attempt some operation 
} 
catch (Exception x) when (x.IsTolerable()) 
{ 
    // log and skip this operation, keep running 
} 

在这个例子中,你会写IsTolerable作为Exception扩展方法,如果最里面的例外是NullReferenceException返回falseIndexOutOfRangeExceptionInvalidCastException您任何其他异常类型已经决定必须表明,必须停止执行,并要求调查一个低级错误。这些是“无法忍受”的情况。

这可能被称为“乐观”的异常处理:假设所有的例外是,除了一组已知黑名单类型的容忍。另一种方法(由C#5及更早版本支持)是“悲观”方法,只有已知白名单的异常被认为是可容忍的,其他任何东西都是未处理的。

年前悲观的做法是官方推荐的立场。但是现在CLR本身会捕获Task.Run中的所有异常,因此它可以在线程之间移动错误。这最终导致块执行。所以CRL是非常乐观的默认情况下

您还可以争取与AppDomain.UnhandledException事件,尽可能多的信息保存为可以用于支持目的(至少堆栈跟踪),然后调用Environment.FailFast到关闭过程之前的任何finally块可以执行(这可能会破坏宝贵的调查错误所需的信息,或抛出隐藏原始错误的其他例外情况)。

+1

@Luaan已更新答案! – 2016-12-07 10:29:32

2

我不知道,真的有一个公认的最佳实践,但IMO

try // form 1: useful only for the logging, and only in debug builds. 
{ 
    //error code 
} 
catch (Exception ex) 
{ 
    throw;// ex; 
} 

是没有真正意义上除了记录方面,所以我只会在调试版本做到这一点。捕捉重新投掷是昂贵的,所以你应该有理由支付这些成本,而不仅仅是你喜欢看代码。

try // form 2: not useful at all 
{ 
    //error code 
} 
catch (Exception ex) 
{ 
    throw new MyCustomException(); 
} 

这一个没有任何意义。这是丢弃真正的异常,并与一个包含关于实际真正的问题较少的信息替换它。我可以看到,如果我想用一些关于正在发生的事情的信息来增强例外,可能会这样做。

try // form 3: about as useful as form 1 
{ 
    //error code 
} 
catch (Exception ex) 
{ 
    throw new MyCustomException(ex, MyContextInformation); 
} 

但我可以说,在几乎所有情况下,你是不是处理异常的最好形式是简单地让更高级别的处理程序处理。

// form 4: the best form unless you need to log the exceptions. 
// error code. no try - let it percolate up to a handler that does something productive. 
4

还没有人指出,你应该考虑的第一件事:什么是威胁

当后端层抛出一个异常,什么可怕的和意想不到的事情发生了。 意想不到的可怕情况可能发生,因为该层受到攻击由一个敌对用户last在这种情况下,您想要做的事情是向攻击者提供出错的所有详细列表以及为什么。当业务逻辑层出现问题时,要做的正确事情是仔细记录有关异常的所有信息,并用通用的“我们很抱歉,出现问题,管理已收到警告,请重试”页面。

需要跟踪的事情之一就是关于用户的所有信息以及发生异常时他们正在做什么。这样,如果您发现同一个用户似乎总是出现问题,那么您可以评估他们是否可能在探测您的弱点,或仅仅使用未经充分测试的应用程序的特殊角落。

获得安全设计权第一个,只有后来担心诊断和调试。

相关问题