设计规范和最佳实践

善用finally块

  • 当使用lock,using,foreach语句或者重写类的析构函数时,编译器自动生成try/finally块。其中try块中放入开发者写的代码;finally块中放入清理资源代码
  1. using(FileStream fs = new FileStream(@"C:\Data.bin", FileMode.Open))
  2. {
  3. Console.WriteLine(100 / fs.ReadByte());
  4. }
  5. // 等价于
  6. FileStream fs = new FileStream(@"C:\Data.bin", FileMode.Open)
  7. try
  8. {
  9. Console.WriteLine(100 / fs.ReadByte());
  10. }
  11. finally
  12. {
  13. fs.close()
  14. }

不要什么异常都捕捉。

如果非要捕捉所有异常的话,最好使用throw重新抛出来交给上层去处理

  • 应该只捕捉可能抛出的异常,所有的异常都捕捉,并且不再throw的话,会造成错误不容易被发现。(个人认为,不要在底层代码吞噬所有未知异常,顶层代码可以适当试图捕获所有异常)

得体的从异常中恢复

  • 如:
  1. try
  2. {
  3. //do somthing
  4. }
  5. catch (IOException ex)
  6. {
  7. IILogBll.Addlog("CompanyBasicInfoImporter.Import", ex);
  8. errorMessage = "无法读取" + fileShortName + "文件或文件格式有误";
  9. }
  10. catch(ZipException ex)
  11. {
  12. IILogBll.Addlog("CompanyBasicInfoImporter.Import", ex);
  13. errorMessage = fileShortName +"文件格式有误";
  14. }
  15. catch (POIXMLException ex)
  16. {
  17. IILogBll.Addlog("CompanyBasicInfoImporter.Import", ex);
  18. errorMessage = fileShortName +"文件格式有误";
  19. }
  • 发生不可恢复的异常时回滚部分已完成操作 如:

    1. try
    2. {
    3. // 记录操作前的状态
    4. // 开始操作
    5. }
    6. catch (Exception ex)
    7. {
    8. // 回滚操作
    9. // 重新抛出,让上层代码知道发生了什么
    10. throw;
    11. }

发生不可恢复的异常时回滚部分完成的操作

  • 将文件恢复为任何对象序列化之前的状态。以下代码演示了正确的实现方式:
  1. public void SerializeObjectGraph(FileStream fs, IFormatter formatter, Object rootObj) {
  2. // Save the current position of the file.
  3. Int64 beforeSerialization = fs.Position;
  4. try {
  5. // Attempt to serialize the object graph to the file.
  6. formatter.Serialize(fs, rootObj);
  7. }
  8. catch { // Catch any and all exceptions.
  9. // If ANYTHING goes wrong, reset the file back to a good state.
  10. fs.Position = beforeSerialization;
  11. // Truncate the file.
  12. fs.SetLength(fs.Position);
  13. // NOTE: The preceding code isn't in a finally block because
  14. // the stream should be reset only when serialization fails.
  15. // Let the caller(s) know what happened by re ­throwing the SAME exception.
  16. throw;
  17. }
  18. }

隐藏实现细节来维系协定

也就是在捕获的异常里面抛出调用者希望抛出的异常。

  • 有时,开发人员之所以捕捉一个异常并抛出一个新异常,目的是在异常中添加额外的数据或上下文。然而,如果这是你唯一的目的,那么只需捕捉希望的异常类型,在异常对象的Data属性(一个键值对的集合)中添加数据,然后重新抛出相同的异常对象。
  1. public String GetPhoneNumber(String name)
  2. {
  3. String phone;
  4. FileStream fs = null;
  5. try
  6. {
  7. fs = new FileStream(_filePath, FileMode.Open);
  8. phone = /* 已找到的电话号码 */
  9. }
  10. catch (FileNotFoundException e)
  11. {
  12. throw new NameNotFoundException(name, e);
  13. }
  14. catch(IOException e)
  15. {
  16. throw new NameNotFoundException(name, e);
  17. }
  18. finally
  19. {
  20. if (fs != null)
  21. {
  22. fs.Close();
  23. }
  24. }
  25. return phone;
  26. }
  27. [Serializable]
  28. internal class NameNotFoundException : Exception
  29. {
  30. public NameNotFoundException(string message, Exception innerException) : base(message, innerException)
  31. {
  32. }
  33. }
  • 该例中,调用方法的用户不需要知道方法实现的细节,他们不需要知道通讯录是保存在文件中还是数据库等载体,只要知道NameNotFoundException这个异常就好了,将原来的异常作为新异常的内部异常,这样便于除错。

有时候,开发人员捕获了一个异常,仅仅是希望再添加一些说明信息后再抛出,这时候可以在Data属性中添加数据如:

  1. try
  2. {
  3. // 开始操作
  4. }
  5. catch (Exception ex)
  6. {
  7. ex.Data.Add("fileName", fileName);
  8. throw;
  9. }