我应该抓住并包装一般的例外吗?

mat*_*i82 13 .net c# exception-handling exception

以下代码可以被视为一种良好做法吗?如果没有,为什么?

try
{
    // code that can cause various exceptions...
}
catch (Exception e)
{
    throw new MyCustomException("Custom error message", e);
}
Run Code Online (Sandbox Code Playgroud)

Iai*_*way 27

简短回答:除非有某些原因,否则不要这样做.相反,捕获您可以处理它们时可以处理的特定异常,并允许所有其他异常冒泡堆栈.


TL; DR回答:这取决于你正在编写的内容,调用代码的内容,以及为什么你觉得需要引入自定义异常类型.

我想,最重要的问题是,如果我抓住了,我的来电者会得到什么好处

  • 不要隐藏调用者需要的信息
  • 不要求你的呼叫者跳过必要的箍圈

想象一下,您正在处理一个低级数据源; 也许你正在做一些编码或反序列化.我们的系统很不错,模块化的,所以你不必了解太多信息, 什么你的低级别的数据源,或者你的代码是如何被调用.也许您的库部署在侦听消息队列并将数据写入磁盘的服务器上.也许它在桌面上,数据来自网络并显示在屏幕上.

可能会发生许多不同的异常(DbException,IOException,RemoteException等),并且您没有处理它们的有用方法.

在这种情况下,在C#中,普遍接受的事情是让异常冒泡.您的呼叫者知道该怎么做:桌面客户端可以提醒用户检查他们的网络连接,该服务可以写入日志并允许新消息排队.

如果将异常包装在自己的MyAwesomeLibraryException中,则会使调用者的工作更加困难.您的来电者现在需要: -

  • 阅读并理解您的文档
  • 在他们想要捕获的点上引入对程序集的依赖
  • 编写额外的代码来询问您的自定义异常
  • 他们不关心的Rethrow例外.

确保额外的努力值得他们的时间.不要无缘无故!

如果您的自定义异常类型的主要理由是为了向用户提供友好的错误消息,那么您应该警惕在捕获的异常中过于非特定.我已经失去了一些次数 - 无论是作为用户还是作为工程师 - 一个过于热心的捕获声明隐藏了问题的真正原因: -

try
{
  GetDataFromNetwork("htt[://www.foo.com"); // FormatException?

  GetDataFromNetwork(uriArray[0]); // ArrayIndexOutOfBounds?

  GetDataFromNetwork(null); // ArgumentNull?
}
catch(Exception e)
{
  throw new WeClearlyKnowBetterException(
    "Hey, there's something wrong with your network!", e);
}
Run Code Online (Sandbox Code Playgroud)

或者,另一个例子: -

try
{
  ImportDataFromDisk("C:\ThisFileDoesNotExist.bar"); // FileNotFound?

  ImportDataFromDisk("C:\BobsPrivateFiles\Foo.bar"); // UnauthorizedAccess?

  ImportDataFromDisk("C:\NotInYourFormat.baz"); // InvalidOperation?

  ImportDataFromDisk("C:\EncryptedWithWrongKey.bar"); // CryptographicException?
}
catch(Exception e)
{
  throw new NotHelpfulException(
    "Couldn't load data!", e); // So how do I *fix* it?
}
Run Code Online (Sandbox Code Playgroud)

现在我们的调用者必须解开我们的自定义异常,以便告诉用户实际出错了什么.在这些情况下,我们已经要求我们的来电者做额外的工作而没有任何好处.引入包含所有异常的自定义异常类型本质上不是一个好主意.一般来说,我: -

  1. 尽我所能抓住最具体的例外

  2. 在这一点上,我可以做点什么

  3. 否则,我只是让异常冒出来

  4. 请记住,隐藏错误的细节并不常见

这并不意味着你永远不应该这样做!

  1. 有时Exception是您可以捕获的最具体的异常,因为您希望以相同的方式处理所有异常 - 例如Log(e); Environment.FailFast();

  2. 有时候你有上下文来处理它被抛出的异常 - 例如你刚刚尝试连接到网络资源而你想重试

  3. 有时调用者的性质意味着你不能允许异常冒泡 - 例如你正在编写日志代码而你不希望日志记录失败"替换"你想要记录的原始异常!

  4. 有时会有一些有用的额外信息,你可以给你的调用者抛出一个在调用堆栈的高处不可用的例外 - 例如在我上面的第二个例子中,我们可以catch (InvalidOperationException e)包括我们正在使用的文件的路径.

  5. 偶尔有什么地方出了错不一样重要哪里出了问题.区分a FooModuleExceptionBarModuleException不管实际问题是什么可能是有用的- 例如,如果存在某些异步,可能会阻止您有用地查询堆栈跟踪.

  6. 虽然这是一个C#问题,但值得注意的是,在其他一些语言(特别是Java)中,您可能会被强制换行,因为已检查的异常是方法合同的一部分 - 例如,如果您正在实现接口,并且接口不是指定该方法可以抛出IOException.

不过,这些都是相当普遍的东西.更具体地说,你的情况:为什么你觉得你需要自定义异常类型?如果我们知道这一点,我们可以为您提供更好的定制建议.

  • 谢谢你彻底的答复.假设有一部分应用程序想要执行操作X,所有调用代码关心的是此操作是否成功.它并不关心究竟是什么错误.例如,我想从文件c:\ TEST.TXT加载文本,如果不可能(无论出于何种原因),则使用默认的硬编码文本.因此,如果文件无法加载,那么我不关心确切的原因(例如.文件不存在,权限被拒绝,文件内容是二进制而不是ascii ...),我关心的是行动是否成功或不.那么在这种情况下使用OP代码是否可以? (2认同)
  • 知道是否或如何处理异常通常需要知道异常发生的位置。包装提供了该信息。它还需要知道是否引发了异常这一事实是否已经造成了导致原始异常的新问题。包装有时也可以提供这种包装。如果方法的协定规定在调用者可能期望的特定情况下抛出某种特定类型的异常,但由于某种意外原因,它调用的一种恰巧抛出同一类型的异常的方法将无法包装异常。 (2认同)

nos*_*tio 6

埃里克·利珀特(Eric Lippert)有一篇很棒的博客文章“烦恼的例外”。埃里克(Eric)用一些通用准则回答了您的问题。以下是“总结”部分的引文:

  • 不要捕获致命的异常;无论如何,您对它们无能为力,而且通常会使情况更糟。
  • 修复您的代码,以使其永远不会触发混乱的异常-生产代码中永远不会发生“索引超出范围”异常。
  • 通过调用在非异常情况下抛出的那些烦恼方法的“ Try”版本,尽可能避免烦恼异常。如果您无法避免调用烦恼方法,请捕获其烦恼异常。
  • 始终处理指示意外的外部条件的异常;通常,预测每种可能的故障都不值得或不切实际。只需尝试操作并准备处理异常即可。

  • 只是为了帮助搜索 - 该博客的 URL 不再有效 - 但在这里:https://ericlippert.com/2008/09/10/veshing-exceptions/ (它可能很旧,但我认为它仍然是一个)对不同类型的异常进行分类的最佳方法,并讨论应用程序对它们的处理应该如何不同)。 (2认同)

das*_*ght 5

不,通常,您不应该这样做:这可能掩盖了真正的异常,这可能表明代码中存在编程问题。例如,如果try/中的代码catch有错误的行,从而导致数组索引超出范围错误,则您的代码也将捕获该错误,并为其引发自定义异常。自定义异常现在已无意义,因为它报告了一个编码问题,因此没有人在您的代码之外捕捉到该异常,便可以对其进行有意义的处理。

另一方面,如果try/ 内的代码catch引发您期望的异常,则将它们捕获并包装在自定义异常中是个好主意。例如,如果您的代码从某个组件专用的特殊文件中读取,并且读取导致一个I / O异常,则捕获该异常并报告一个自定义异常是一个好主意,因为它可以帮助您对调用者隐藏文件操作。

  • 他将原始异常作为内部异常传递给自定义异常,因此这实际上可以 (6认同)
  • @dasblinkenlight与您不同意-自定义异常比此catch块捕获的异常更高级。如果要初始化应用程序,它将类似于`ConfigurationException(“无法初始化交易者模块”,e)之类的东西。高级客户端(例如某些服务)将处理配置错误,而不是玩低级的`IndexOutOfRangeException`和`FileNotFoundException`(以及基本的`Exception`) (3认同)
  • @SergeyBerezovskiy“包装所有内容”方法的根本问题在于,不清楚谁需要调查该异常。如果我只包装有意义的异常,我的客户仍然会带着我的IndexOutOfRangeException来找我,但是他们也许可以自己调查MyCustomException和FileNotFound,而不会打扰我。在有很多客户的情况下,这对我和我的客户来说都可以节省很多时间。 (2认同)

Ser*_*kiy 5

这完全没问题.您不必单独捕获每种异常类型.您可以捕获特定类型的异常,如果你要处理它在特定的方式.如果你想以同样的方式处理所有异常 - 捕获base Exception并像你一样处理它.否则,每个catch块中都会有重复的代码.