设计规范和最佳实践
善用finally块
- 当使用lock,using,foreach语句或者重写类的析构函数时,编译器自动生成try/finally块。其中try块中放入开发者写的代码;finally块中放入清理资源代码
using(FileStream fs = new FileStream(@"C:\Data.bin", FileMode.Open))
{
Console.WriteLine(100 / fs.ReadByte());
}
// 等价于
FileStream fs = new FileStream(@"C:\Data.bin", FileMode.Open)
try
{
Console.WriteLine(100 / fs.ReadByte());
}
finally
{
fs.close()
}
不要什么异常都捕捉。
如果非要捕捉所有异常的话,最好使用throw重新抛出来交给上层去处理
- 应该只捕捉可能抛出的异常,所有的异常都捕捉,并且不再throw的话,会造成错误不容易被发现。(个人认为,不要在底层代码吞噬所有未知异常,顶层代码可以适当试图捕获所有异常)
得体的从异常中恢复
- 如:
try
{
//do somthing
}
catch (IOException ex)
{
IILogBll.Addlog("CompanyBasicInfoImporter.Import", ex);
errorMessage = "无法读取" + fileShortName + "文件或文件格式有误";
}
catch(ZipException ex)
{
IILogBll.Addlog("CompanyBasicInfoImporter.Import", ex);
errorMessage = fileShortName +"文件格式有误";
}
catch (POIXMLException ex)
{
IILogBll.Addlog("CompanyBasicInfoImporter.Import", ex);
errorMessage = fileShortName +"文件格式有误";
}
发生不可恢复的异常时回滚部分已完成操作 如:
try
{
// 记录操作前的状态
// 开始操作
}
catch (Exception ex)
{
// 回滚操作
// 重新抛出,让上层代码知道发生了什么
throw;
}
发生不可恢复的异常时回滚部分完成的操作
- 将文件恢复为任何对象序列化之前的状态。以下代码演示了正确的实现方式:
public void SerializeObjectGraph(FileStream fs, IFormatter formatter, Object rootObj) {
// Save the current position of the file.
Int64 beforeSerialization = fs.Position;
try {
// Attempt to serialize the object graph to the file.
formatter.Serialize(fs, rootObj);
}
catch { // Catch any and all exceptions.
// If ANYTHING goes wrong, reset the file back to a good state.
fs.Position = beforeSerialization;
// Truncate the file.
fs.SetLength(fs.Position);
// NOTE: The preceding code isn't in a finally block because
// the stream should be reset only when serialization fails.
// Let the caller(s) know what happened by re throwing the SAME exception.
throw;
}
}
隐藏实现细节来维系协定
也就是在捕获的异常里面抛出调用者希望抛出的异常。
- 有时,开发人员之所以捕捉一个异常并抛出一个新异常,目的是在异常中添加额外的数据或上下文。然而,如果这是你唯一的目的,那么只需捕捉希望的异常类型,在异常对象的Data属性(一个键值对的集合)中添加数据,然后重新抛出相同的异常对象。
public String GetPhoneNumber(String name)
{
String phone;
FileStream fs = null;
try
{
fs = new FileStream(_filePath, FileMode.Open);
phone = /* 已找到的电话号码 */
}
catch (FileNotFoundException e)
{
throw new NameNotFoundException(name, e);
}
catch(IOException e)
{
throw new NameNotFoundException(name, e);
}
finally
{
if (fs != null)
{
fs.Close();
}
}
return phone;
}
[Serializable]
internal class NameNotFoundException : Exception
{
public NameNotFoundException(string message, Exception innerException) : base(message, innerException)
{
}
}
- 该例中,调用方法的用户不需要知道方法实现的细节,他们不需要知道通讯录是保存在文件中还是数据库等载体,只要知道NameNotFoundException这个异常就好了,将原来的异常作为新异常的内部异常,这样便于除错。
有时候,开发人员捕获了一个异常,仅仅是希望再添加一些说明信息后再抛出,这时候可以在Data属性中添加数据如:
try
{
// 开始操作
}
catch (Exception ex)
{
ex.Data.Add("fileName", fileName);
throw;
}