首页 > 代码库 > 译《异常最佳实践》

译《异常最佳实践》

原文: 《Best Practices for Excetpions》

链接:https://msdn.microsoft.com/en-us/library/seyhszts(v=vs.110).aspx

译文:

设计优秀的应用程序能够处理运行过程中出现的异常和错误以避免应用程序崩溃。这篇文章描述了关于处理与创建异常的最佳实践。

处理异常

  • 合适地使用异常处理代码(try/catch 块)。(因为)同样可以通过编程检查经常可能出现异常的条件(情况)而不使用异常处理。
    if (conn.State != ConnectionState.Closed){    conn.Close();}

    如果上面的代码使用异常处理模型,则如下面的例子所示。使用try/catch 块来包裹连接检查,如果连接没有关闭成功,则抛出异常。

    try{    conn.Close();}catch (InvalidOperationException ex){    Console.WriteLine(ex.GetType().FullName);    Console.WriteLine(ex.Message);}

    是在编程时使用条件检查还是使用异常处理,这取决于你的预期判断——非预期情况是否经常出现。

    • 使用【异常】处理并不经常发生的事件,也就是说使用【异常】处理那些一旦出现就被认为是错误的情况,比如遇到非预期的文件结尾(你认为文件还没有读完,但但得到了文件结尾符或者没有更多的可读内容)。 当你使用异常处理时,比使用一般的条件判断所写的代码要少。
    • 使用编程方式检查那些依惯例会发生的时间、可以被认为是执行的一部分的情况。如果使用编程检查错误,当错误发生时,则需要执行更多的代码。
  • 使用try/catch块包裹执行代码有潜在的产生异常的可能,如果有需要,最好使用finally块来释放资源。在这种模式下,try语句块产生异常,catch块处理异常,finally语句块在不管有没有异常发生的情况下释放已经分配的资源。
  • 在catch块中,处理异常的顺序是从最具体的异常到抽象的异常。这种技巧在异常被传递到更抽象的异常之前被处理掉。

创建和抛出异常

下节内容包含了创建自己的异常、以及这些异常该在合适被抛出的指南。如果向得到更加详细的参考,可以查看《异常设计准则》。

  • 设计的类在一般情况执行下应该永远不要抛出异常。举个例子,FileStream类已经提供了帮助检查文件是否到达结尾的判断方法。这能够帮你避免越过文件结尾还继续读取文件的异常。下面这个例子展示了如何一直读取文件结尾。
    class FileRead{    public void ReadAll(FileStream fileToRead)    {        // This if statement is optional        // as it is very unlikely that        // the stream would ever be null.        if (fileToRead == null)        {            throw new System.ArgumentNullException();        }        int b;        // Set the stream position to the beginning of the file.        fileToRead.Seek(0, SeekOrigin.Begin);        // Read each byte to the end of the file.        for (int i = 0; i < fileToRead.Length; i++)        {            b = fileToRead.ReadByte();            Console.Write(b.ToString());            // Or do something else with the byte.        }    }}
  • 抛出异常以代替返回错误值或者HRESULT的做法。
  • 在错误出现极其平凡的情况下,返回null而不抛出一个异常。极其平常的错误情况可以被认为是一般的控制流。在这种情况下返回null,对应用程序的性能影响最小。
  • 在通常情况下,使用.Net框架预定义的异常类型。只有预定义的类型不够用时才采用一个新的异常类型。
  • 在属性没有被正确设置或者在对象状态不合适的情况下调用某个函数,应该抛出一个InvalidOperationException。
  • 在传递的参数无效的情况下应该抛出ArgumentException或者继承其的异常。
  • 在大部分应用程序中,自定义异常应该继承于Exception类。继承自ApplicationException并不能增加很大的意义。
  • 异常的类名以“Exception”结尾。例如:
    public class MyFileNotFoundException : Exception{}
  • 在C#和C++中,应该至少提供/使用三个最常见的构造函数来创建自定义类实例:默认构造函数;含有一个消息字符串的构造函数;含有一个消息字符串以及一个内部异常参数的构造函数。如果需要一个例子,可以参见《How to: 创建用户自定义异常》
    • Exception(), 无参数构造函数。
    • Exception(string),接受一个消息字符串。
    • Exception(string, Exception),接受一个消息字符串以及一个内部异常实例。  
  • 当你创建用户自定义异常时,必须确保异常的元数据对远程执行或者跨域执行的代码可见。(比较高级,不展开了)
  • 每个异常都应拥有本地化的描述信息。用户看到的异常信息来自于抛出的异常类的描述字符串,而不是异常类(名)。
  • 使用文法正确的错误消息(串),包含结束标点符号。异常描述的每个句子应该以句号结尾。比如,“日志表已经溢出。”是比较合适的描述语句。
  • 提供更多异常属性以供编程访问。当在一个额外信息对编程场景十分有用的情况下,应该为异常提供额外的属性(额外的更多的描述信息)。 
  • 堆栈轨迹开始于throw,结束语catch语句。决定在哪里抛出异常应该意识到这一情况。
  • 使用异常创建函数。一个类的实现中在不同地方抛出相同的异常时十分普遍的,为了避免冗余的代码,可以使用帮助函数创建并返回异常。以下是一个例子:
    class FileReader{    private string fileName;    public FileReader(string path)    {        fileName = path;    }    public byte[] Read(int bytes)    {        byte[] results = FileUtils.ReadFromFile(fileName, bytes);        if (results == null)        {            throw NewFileIOException();        }        return results;    }    FileReaderException NewFileIOException()    {        string description = "My NewFileIOException Description";        return new FileReaderException(description);    }}

    同样也可以选择使用异常类的构造函数来创建异常,但这更适合像ArgumentException这样的全局异常类。

  • 在抛出异常时清理中间结果。异常的调用者必须能够确定一个函数抛出异常不会造成副作用。

译《异常最佳实践》