.Net处理exception时常见的编程错误?
在处理exception时,你所看到的最常见的错误是什么?
看起来exception处理可能是学习如何在.Net中“正确”的最难的事情之一。 特别是考虑到.NET开发人员为了避免常见编程错误而排名第一的答案? 与exception处理有关。
希望通过列举一些最常见的错误,我们都可以学会更好地处理exception。
在处理exception时,你所看到的最常见的错误是什么?
我可以想很多。
首先阅读我关于exception分类的文章,将其归类为令人烦恼的 , 骨头的 , 致命的和外生的 :
http://ericlippert.com/2008/09/10/vexing-exceptions/
一些常见的错误:
- 未能处理外部例外。
- 未能处理令人头痛的exception。
- 抛出令人烦恼的例外的方法的构build。
- 处理实际上无法处理的exception,如致命exception。
-
处理隐藏代码中的错误的exception; 不要处理一个头骨的exception,修复这个bug,使其不被抛出
-
安全错误: 失败到不安全模式
try { result = CheckPassword(); if (result == BadPassword) throw BadPasswordException(); } catch(BadPasswordException ex) { ReportError(ex); return; } catch(Exception ex) { LogException(ex); } AccessUserData();
看看发生了什么? 我们没有采取不安全的模式。 如果CheckPassword抛出NetworkDriverIsAllMessedUpException,那么我们抓住它,logging它并访问用户的数据,而不pipe密码是否正确 。 没有安全模式; 当你得到任何exception,承担最坏的。
-
安全错误:直接或间接产生泄露敏感信息的exception。
这不完全是关于处理代码中的exception,而是关于生成由恶意代码处理的exception。
好笑的故事。 在.NET 1.0发布给客户之前,我们发现了一个可能调用引发exception的方法的错误:“调用此方法的程序集没有权限确定文件C:\ foo.txt的名称”。 大。 谢谢你让我知道。 什么是阻止说汇编捕捉exception和询问其消息来获取文件名? 没有。 我们在发货之前解决了这个问题
这是一个直接的问题。 一个间接的问题将是我在
LoadPicture
,VBScript中实现的一个问题。 它给出了一个不同的错误消息,这取决于错误的参数是一个目录,一个不是图片的文件,还是一个不存在的文件。 这意味着你可以使用它作为一个非常慢的磁盘浏览器! 通过尝试一大堆不同的东西,你可以逐渐build立一个什么文件和目录在别人的硬盘上的图片。 应该devise例外,以便如果由不可信的代码处理它们,则该代码不会从用户所做的任何事情中获知用户的私人信息,从而导致exception。 (LoadPicture现在提供的帮助较less的错误消息。) -
安全和资源pipe理错误:不清理资源的处理程序是等待发生的资源泄漏 。 资源泄漏可以被恶意的部分信任代码用作拒绝服务攻击,故意产生exception情况。
-
健壮性错误:处理程序必须假定程序状态是混乱的,除非处理特定的外生exception。 最后的块尤其如此。 当你处理一个意想不到的exception时,这是完全可能的,甚至有可能是你的程序中的东西被深深地搞砸了。 你不知道你的任何子系统是否正在工作,如果是,调用它们会使情况变好或变坏。 集中logging错误并尽可能保存用户数据,并尽可能干净地closures。 假设没有什么正确的。
-
安全性错误: 在任何可能具有敌意的代码可以运行之前 ,需要撤销具有安全影响的临时全局状态突变 。 恶意代码可以在最后运行之前运行! 有关详细信息,请参阅我的文章:
http://blogs.msdn.com/ericlippert/archive/2004/09/01/224064.aspx
重新抛出这样的exception:
try { // some code here } catch(Exception ex) { // logging, etc throw ex; }
这杀死了堆栈跟踪,使得可用性更低。 重新抛出的正确方法是这样的:
try { // some code here } catch(Exception ex) { // logging, etc throw; }
在很多情况下捕捉所有的exception情况,您应该尝试捕捉特定的exception情况:
try { // Do something. } catch (Exception exc) { // Do something. }
而不是:
try { // Do something. } catch (IOException exc) { // Do something. }
例外情况应该从最具体到最less的顺序排列。
用一个毫无意义的信息重新审视一个例外。
try { ... } catch (Exception ex) { throw new Exception("An error ocurred when saving database changes"). }
你不会相信我经常看到这样的代码在生产中运行。
没有人在谈论看到像这些空的catch块…
try{ //do something } catch(SQLException sqex){ // do nothing }
也从来没有使用exception处理来创build备用方法stream程…
try{ //do something }catch(SQLException sqex){ //do something else }
不使用IDisposable
对象 :
File myFile = File.Open("some file"); callSomeMethodWhichThrowsException(myFile); myFile.Close();
在myFile.Close()
之前抛出myFile的终结器(这可能永远不会),因为myFile没有closures。
正确的做法是
using(File myFile = File.Open("some file")) { callSomeMethodWhichThrowsException(myFile); }
这被编译器翻译成如下所示:
File myFile = File.Open("some file"); try { callSomeMethodWhichThrowsException(myFile); } finally { if(myFile != null) myFile.Dispose(); //Dispose() calls Close() }
所以即使在例外的情况下文件也会被closures。
当重新抛出一个捕获的exception时,忘记设置内部exception
try { ... } catch (IOException ioException) { throw new AppSpecificException("It was not possible to save exportation file.") // instead of throw new AppSpecificException("It was not possible to save exportation file.", ioException); }
当我发布这个答案时,我忘了提及,由于安全原因,我们应该总是考虑何时包含内部exception。 正如Eric Lippert 针对这个主题的另一个答案所指出的那样,一些例外可以提供关于服务器实现细节的敏感信息。 因此,如果将要处理exception的调用者不被信任,那么包含内部exception信息并不是一个好主意。
清空:
//What's the point? catch() {}
重新抛出:
//Exceptions are for *adding* detail up the stack catch (Exception ex) {throw ex;}
假设一个涵盖许多场景的例外是特定的。 一个真实的场景是一个Web应用程序,其中exception处理始终假定所有错误都是会话超时并logging,并在会话超时时报告所有错误。
另一个例子:
try { Insert(data); } catch (SqlException e) { //oh this is a duplicate row, lets change to update Update(data); }
loggingException.Message而不是Exception.ToString()
很多时候,我看到代码只loggingexception消息,而应该loggingToString方法的返回值。 ToString提供了比Message更多的关于exception的信息。 它包含除了消息外的内部exception和堆栈跟踪等信息。
试图捕获OutOfMemoryException或StackOverflowException – 那些导致运行时closures,从而在同一个进程(甚至从整个CLR)中捕获它们的方法?
OutOfMemoryException:当没有足够的内存继续执行程序时引发的exception。
“从.NET Framework 2.0版开始,StackOverflowException对象不能被try-catch块捕获,相应的进程默认被终止,因此build议用户编写代码来检测和防止堆栈溢出。
无法捕获捕获处理程序内可能的exception。 这会导致错误的exception向上传播。
例如:
try { DoImportantWork(); } catch { Cleanup(); throw; }
如果Cleanup()
抛出一个exception会发生什么? 您不希望在此捕获处理程序中看到指向Cleanup()方法的exception。 你想要原来的错误。 您可以尝试logging清除错误,但即使您的日志logging代码需要exception处理,以避免引发exception。
try { DoImportantWork(); } catch { try { Cleanup(); } catch { // We did our best to clean up, and even that failed. // If you try to log this error, the logging may throw yet another Exception. } throw; }
错误
try { // Do something stupid } catch { // ignore the resulting error because I'm lazy, and later spend // a week trying to figure out why my app is crashing all over // the place. }
更好
try { /// do something silly. } catch (InvalidOperationException ex) { /// respond, or log it. } catch (Exception e) { /// log it. }
正常stream量控制使用例外。 例外情况应例外。 如果这是一个好的/预期的操作,则使用返回值等。