一个被良好设计的错误处理代码块集可以让程序更健壮并且面临更少的崩溃机会,因为这样的应用程序对错误进行了处理。下面的列表包含了异常处理最佳习惯中的建议:
- 知道什么时候要设立 try/catch 块。例如,你可以通过编程来检查可能发生在使用异常处理之外的条件。而在其他情况下,就应该适当地使用异常处理来捕获错误条件。
以下范例使用了一个 if 语句来检查连接是否已经关闭。你可以使用这个方法来代替如果连接没有被关闭就抛出一个异常的做法。
if(conn.State != ConnectionState.Closed) conn.Close();
在以下范例中,一个异常被抛出,如果连接没有被关闭的话。
try { conn.Close(); } catch(InvalidOperationException ex) { //以该错误做一些事情或者忽略它。 }
你选择的该方法依赖于你如何经常期望该事件的产生。如果该事件是真实的异常并且是一个错误(比如一个未预料的文件结尾),那么使用异常处理就是更好的,因为只有更少的代码才会在常规场合中被执行。如果该事件经常发生,那最好是使用可编程的方法来检查错误。在这种情况下,如果产生一个异常,该异常就将获得更长的处理时间。
- 使用 try/finally 块包围可能潜在被产生的异常并且把你的 catch 语句集中到一个位置的代码。这样,try 语句就会产生异常,而 finally 语句就会关闭或者取消资源的分配,并且 catch 语句还会从一个集中的位置来对异常进行处理。
- 始终按照最特殊到最不特殊的顺序来排列 catch 块中的异常。这个技术会在它被传递到一个更普通的 catch 块之前对特殊的异常进行处理。
- 以单词[Exception]作为异常类名称的结尾。例如:
public class MyFileNotFoundException : ApplicationException { }
- 当创建用户定义的异常时,你必须确保该异常的元数据可用于远程代码执行,包括跨越应用程序域而产生的异常。例如,假设 Application Domain A 创建了执行了抛出异常的代码的 Application Domain B。为了让 Application Domain A 适当地捕获并且处理该异常,它必须可以找到包含了由 Application Domain B 所抛出的异常的汇编集。如果 Application Domain B 在它的应用程序基础之下抛出了一个包含在汇编集中的异常,但不是在 Application Domain A 的应用程序基础之下,那么 Application Domain A 将无法找到该异常并且公共语言运行时还会抛出 FileNotFoundException。要避免出现这种情况,你可以布署以两种方式来包含异常信息的汇编集:
- 把汇编集放到一个同时被两个应用程序域所共享的公共应用程序基础中
- 或者 -
- 如果该域没有共享公共应用程序基础,就以强名称来标记该汇编集来包含异常信息并且把该汇编集布署到全局汇编缓存中。
- 把汇编集放到一个同时被两个应用程序域所共享的公共应用程序基础中
- 在 C# 与 C++ 中,在创建你自己的异常类时至少使用三种公共的构造器。关于范例,参考[使用用户定义的异常]。
- 在多数情况下,可以使用预定义的异常类型。只在可编程情况下才定义新的异常类型。引入一个新的异常类来允许程序员在基于该异常类的代码中采取其他的动作。
- 大部分应用程序都可以从 Exception 类派生出自定义异常。这是自定义异常应该派生自 ApplicationException 类的最初考虑;然而并没有发现这会从习惯上增加重要的价值。
- 在每个异常中都包括一个本地化的描述字符串。当用户看到错误消息的时候,这就是从派生自被抛出异常的类的描述字符串,胜于从异常类中。
- 使用正确的文法错误消息,包括结尾标点符号。异常描述字符串中的每个句子都应该以一个句号结尾。
- 为可编程访问而提供 Exception 属性。只在额外信息是有用的可编程情况下才包括异常的额外信息(除了描述字符串之外)。
- 为非常公共的错误情况而返回 null。例如,File.Open 会返回 null,如果该文件没有被找到的话,但是抛出一个异常,如果该文件被发现的话。
- 设计从不会在常规使用中抛出异常的类。例如,一个 FileStream 类暴露了检测是否已经到达文件结尾的其他方式。这就避免了抛出异常,如果你读取到到文件结尾之外的话。下列范例说明了如何读取到文件的结尾。
class FileRead { void Open() { FileStream stream = File.Open("myfile.txt", FileMode.Open); byte b; // ReadByte 在 EOF 返回 -1。 while ((b == stream.ReadByte()) != true) { // 做些什么。 } } }
- 抛出一个 InvalidOperationException,如果一个属性集或者方法调用没有适当地提供对象的当前状态的话。
- 抛出一个 ArgumentException 或者一个派生自 ArgumentException 的类,如果传递了无效的参数的话。
- 堆栈追踪开始于异常被抛出的语句并且结束于捕获该异常的 catch 语句。在你决定在哪里存放一个 throw 语句的时候就应该明白这个事实。
- 通常为一个类从它的实现中的不同位置抛出相同异常的公共方式而使用异常建立器方法。为了避免额外的代码,可以使用创建异常并且将其返回的辅助方法。例如:
class File { string fileName; public byte[] Read(int bytes) { if (!ReadFile(handle, bytes)) throw NewFileIOException(); } FileException NewFileIOException() { string description = // 建立本地化的字符串,包括 fileName。 return new FileException(description); } }
或者使用异常的构造器来建立该异常。这更加适合于全局异常类,比如 ArgumentException。
- 抛出异常来代替返回错误代码或者 HRESULT。
- 在抛出异常的时候清理中间结果。调用者应该可以假设在异常从方法中被抛出的时候没有负面影响。