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中,则会使调用者的工作更加困难.您的来电者现在需要: -
确保额外的努力值得他们的时间.不要无缘无故!
如果您的自定义异常类型的主要理由是为了向用户提供友好的错误消息,那么您应该警惕在捕获的异常中过于非特定.我已经失去了一些次数 - 无论是作为用户还是作为工程师 - 一个过于热心的捕获声明隐藏了问题的真正原因: -
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)
现在我们的调用者必须解开我们的自定义异常,以便告诉用户实际出错了什么.在这些情况下,我们已经要求我们的来电者做额外的工作而没有任何好处.引入包含所有异常的自定义异常类型本质上不是一个好主意.一般来说,我: -
尽我所能抓住最具体的例外
在这一点上,我可以做点什么
否则,我只是让异常冒出来
请记住,隐藏错误的细节并不常见
这并不意味着你永远不应该这样做!
有时Exception是您可以捕获的最具体的异常,因为您希望以相同的方式处理所有异常 - 例如Log(e); Environment.FailFast();
有时候你有上下文来处理它被抛出的异常 - 例如你刚刚尝试连接到网络资源而你想重试
有时调用者的性质意味着你不能允许异常冒泡 - 例如你正在编写日志代码而你不希望日志记录失败"替换"你想要记录的原始异常!
有时会有一些有用的额外信息,你可以给你的调用者抛出一个在调用堆栈的高处不可用的例外 - 例如在我上面的第二个例子中,我们可以catch (InvalidOperationException e)包括我们正在使用的文件的路径.
偶尔有什么地方出了错不一样重要哪里出了问题.区分a FooModuleException和BarModuleException不管实际问题是什么可能是有用的- 例如,如果存在某些异步,可能会阻止您有用地查询堆栈跟踪.
虽然这是一个C#问题,但值得注意的是,在其他一些语言(特别是Java)中,您可能会被强制换行,因为已检查的异常是方法合同的一部分 - 例如,如果您正在实现接口,并且接口不是指定该方法可以抛出IOException.
不过,这些都是相当普遍的东西.更具体地说,你的情况:为什么你觉得你需要自定义异常类型?如果我们知道这一点,我们可以为您提供更好的定制建议.
埃里克·利珀特(Eric Lippert)有一篇很棒的博客文章“烦恼的例外”。埃里克(Eric)用一些通用准则回答了您的问题。以下是“总结”部分的引文:
- 不要捕获致命的异常;无论如何,您对它们无能为力,而且通常会使情况更糟。
- 修复您的代码,以使其永远不会触发混乱的异常-生产代码中永远不会发生“索引超出范围”异常。
- 通过调用在非异常情况下抛出的那些烦恼方法的“ Try”版本,尽可能避免烦恼异常。如果您无法避免调用烦恼方法,请捕获其烦恼异常。
- 始终处理指示意外的外部条件的异常;通常,预测每种可能的故障都不值得或不切实际。只需尝试操作并准备处理异常即可。
不,通常,您不应该这样做:这可能掩盖了真正的异常,这可能表明代码中存在编程问题。例如,如果try/中的代码catch有错误的行,从而导致数组索引超出范围错误,则您的代码也将捕获该错误,并为其引发自定义异常。自定义异常现在已无意义,因为它报告了一个编码问题,因此没有人在您的代码之外捕捉到该异常,便可以对其进行有意义的处理。
另一方面,如果try/ 内的代码catch引发您期望的异常,则将它们捕获并包装在自定义异常中是个好主意。例如,如果您的代码从某个组件专用的特殊文件中读取,并且读取导致一个I / O异常,则捕获该异常并报告一个自定义异常是一个好主意,因为它可以帮助您对调用者隐藏文件操作。
这完全没问题.您不必单独捕获每种异常类型.您可以捕获特定类型的异常,如果你要处理它在特定的方式.如果你想以同样的方式处理所有异常 - 捕获base Exception并像你一样处理它.否则,每个catch块中都会有重复的代码.