C#中的异常处理原则
前言:写代码写的多了,越来越觉得异常处理的重要性,在出现异常的时候一是能保证程序不至于崩溃,给客户一个黄页或者windows的停止响应,二是迅速定位到错误的位置,因为很多时候,一个完整的多人合作的项目,也不是很方便去调试,就比如最近在做的sharepoint网站的项目,一次调试不仅要占用开发机的IIS资源,而且还需要很长的时间,效率是非常低的。而完善的异常处理和日志,能救人于水火之中啊,闲话少说,下面进入主题
ps:写这篇的时候,资料不在手头,很痛苦。。。
- 异常类exception的内部结构已经了解了
- 系统级异常和应用程序级异常的区别
- 自定义异常类最佳实践原则
- java中异常处理的原则
- //log4net使用指南
0.1 错误、bug和异常
这个概念也是最近才有的,其实我们平时说的bug和异常,其实是两个东西。
- bug:简单来说,是由程序员引起的错误,比如超出索引。
- 用户错误:和bug不同,用户错误往往不是由应用程序作者,而是由运行程序的用户引起的错误。例如代码中没有处理错误输入,而用户在文本框中输入了格式非法的字符串时,会产生用户错误。
- 异常:异常往往是运行时的非正常情况,在编程时很难被估计到。异常可能包括:链接一个不存在的数据库,打开已经被破坏的xml文件。在上述各种情况下,程序员和最终用户都无法完全控制这些异常情况。
1.1 异常的基类机构和各个属性的定义
异常的基类结构如下
namespace System
{
//
// 摘要:
// 表示在应用程序执行过程中发生的错误。
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(_Exception))]
[ComVisible(true)]
public class Exception : ISerializable, _Exception
{
public Exception();
public Exception(string message);
public Exception(string message, Exception innerException);
[SecuritySafeCritical]
protected Exception(SerializationInfo info, StreamingContext context);
// 获取一个提供用户定义的其他异常信息的键/值对的集合。
public virtual IDictionary Data { get; }
public virtual string HelpLink { get; set; }
// 获取或设置 HRESULT(一个分配给特定异常的编码数字值)。
public int HResult { get; protected set; }
// 一个 Exception 的实例,描述导致当前异常的错误。InnerException 属性返回与传递给构造函数的值相同的值,或者,如果没有向构造函数提供内部异常值,则返回
public Exception InnerException { get; }
public virtual string Message { get; }
// 导致错误的应用程序或对象的名称。
public virtual string Source { get; set; }
// 获取调用堆栈上直接帧的字符串表示形式。
public virtual string StackTrace { get; }
// 获取引发当前异常的方法。
public MethodBase TargetSite { get; }
// 在序列化异常,以创建包含有关异常的序列化数据的异常状态对象时发生。
protected event EventHandler<SafeSerializationEventArgs> SerializeObjectState;
// 异常链中第一个被引发的异常。如果当前异常的 System.Exception.InnerException 属性是 null 引用(Visual Basic
// 中为 Nothing),则此属性返回当前异常。
public virtual Exception GetBaseException();
// 当在派生类中重写时,用关于异常的信息设置 System.Runtime.Serialization.SerializationInfo。
[SecurityCritical]
public virtual void GetObjectData(SerializationInfo info, StreamingContext context);
public Type GetType();
public override string ToString();
}
}
在构建自定义异常处理类的时候,使用exception的代码段可以自动生成自定义异常处理类的结构。并且,仅在出现错误的类与该错误关系紧密时,才需要创建自定义的异常。
ps:在捕获异常时候,使用try;catch;finally,throw;四个关键字来构架你自己的异常处理流程。
2.1 异常处理的原则
学习了一个Java的博客,总结的很好,异常处理的原则有三个:
- 具体明确
- 提早抛出
- 延迟捕获
具体明确
比如具体的业务场景读取文件,可能会出现文件找不到FileNotFoundException,或者文件名太长PathTooLongException等等许多不同类型的exception,而且不同的exception的处理方式不同,比如找不到文件可能需要用户再次指定准确的路径,文件名太长让用户修改文件名之后重试等等,因为一个try是允许多个catch块来处理的,越是捕获到明确的异常,越是方便后续的处理。
提早抛出
异常堆栈信息提供了导致异常出现的方法调用链的精确顺序,包括每个方法调用的类名,方法名,代码文件名甚至行数,以此来精确定位异常出现的现场。通过提早抛出异常(又称"迅速失败"),异常得以清晰又准确。堆栈信息立即反映出什么出了错(提供了非法参数值),为什么出错(文件名不能为空值),以及哪里出的错
另外,其中包含的异常信息("文件名为空")通过明确回答什么为空这一问题使得异常提供的信息更加丰富,而这一答案是我们之前代码中抛出的NullPointerException所无法提供的。
通过在检测到错误时立刻抛出异常来实现迅速失败,可以有效避免不必要的对象构造或资源占用,比如文件或网络连接。同样,打开这些资源所带来的清理操作也可以省却。
延迟捕获
这个是我觉得最重要的一个原则,不要在程序有能力处理异常的时候就去捕获,尤其是许多底层方法和公共方法,因为你并不清楚他的上层调用会对这种情况作出怎样的处理,比如文件路径为空,有些时候上层程序可能知道另一个备用地址,有些时候就需要用户重新输入,是不一样的。