Item 44: Create Complete Application-Specific Exception Classes
创建完全和应用程序相关的异常类
Exceptions are the mechanism of reporting errors that might be handled at a location far removed from the location where the error occurred. All the information about the error's cause must be contained in the exception object. Along the way, you might want to translate a low-level error to more of an application-specific error, without losing any information about the original error. You need to be very thoughtful about when you create your own specific exception classes in your C# applications.
异常是报告错误的机制,这些错误可能发生在一个地方,处理时又在比较远的地方。所有关于错误的原因都应该被包含在异常对象里。同样,你可能希望将一个低层次的错误传输到应用程序级的错误,同时不损失任何关于原来错误的信息。在C#应用程序里面,你需要对在哪里创建自己的异常类进行充分的考虑。
The first step is to understand when and why to create new exception classes, and how to construct informative exception hierarchies. When developers using your libraries write catch clauses, they differentiate actions based on the specific runtime type of the exception. Each different exception class can have a different set of actions taken:
第一步是:理解何时以及为什么要创建新的异常类,以及如何构建具有信息性的异常体系。当使用你的库的开发者编写catch子句时,他们不同的行为则基于异常的运行时类型。每个不同的异常类可以有不同的要采取的动作的集合。
- try
- {
- Foo( );
- Bar( );
- }
- catch( MyFirstApplicationException e1 )
- {
- FixProblem( e1 );
- }
- catch( AnotherApplicationException e2 )
- {
- ReportErrorAndContinue( e2 );
- }
- catch( YetAnotherApplicationException e3 )
- {
- ReportErrorAndShutdown( e3 );
- }
- catch( Exception e )
- {
- ReportGenericError( e );
- }
- finally
- {
- CleanupResources( );
- }
Different catch clauses can exist for different runtime types of exceptions. You, as a library author, must create or use different exception classes when catch clauses might take different actions. If you don't, your users are left with only unappealing options. You can punt and terminate the application whenever an exception gets thrown. That's certainly less work, but it won't win kudos from users. Or, they can reach into the exception to try to determine whether the error can be corrected:
不同的catch子句会因为不同的异常运行时类型而退出。你,作为库的作者,当catch子句将采取不同的行为时,必须创建并使用不同的异常类。如果你不这样做,那么用户将只能面对一个不吸引人的选择。当一个异常被抛出时,你要挂起或者终止应用程序。那样当然不要很多工作,但是却不能获得用户的支持。否则,他们能认识该异常,并尝试决定该错误是否能被修正:
- try
- {
- Foo( );
- Bar( );
- }
- catch( Exception e )
- {
- switch( e.TargetSite.Name )
- {
- case "Foo":
- FixProblem( e );
- break;
- case "Bar":
- ReportErrorAndContinue( e );
- break;
- // some routine called by Foo or Bar:
- default:
- ReportErrorAndShutdown( e );
- break;
- }
- }
- finally
- {
- CleanupResources( );
- }
That's far less appealing than using multiple catch clauses. It's very brittle code: If you change the name of a routine, it's broken. If you move the error-generating calls into a shared utility function, it's broken. The deeper into the call stack that an exception is generated, the more fragile this kind of construct becomes.
这比使用多个catch子句还要缺少吸引力。这样的代码相当脆弱:如果你改变了程序的名字,就破坏了程序。如果你将生成错误的调用移到一个功效的有效的方法,也破坏程序。对生成异常的调用堆栈越深入,这样的结构就变得越脆弱。
Before going any deeper into this topic, let me add two disclaimers. First, exceptions are not for every error condition you encounter. There are no firm guidelines, but I prefer throwing exceptions for error conditions that cause long-lasting problems if they are not handled or reported immediately. For example, data integrity errors in a database should generate an exception. The problem only gets bigger if it is ignored. Failure to correctly write the user's window location preferences is not likely to cause far-reaching consequences. A return code indicating the failure is sufficient.
在对该主题进行的更深入之前,让我先说两个不能承诺的声明。第一,异常不适用于你遇到的每种错误情况。没有固定的指导原则,有些错误,如果不被迅速处理或者报告的话,可能会引起持续很久的问题,对于这样的情况,我喜欢抛出异常。例如,数据库里面的数据完整性错误应该生成异常。如果它被忽略的话,问题将会变得更严重。对于不能正确的创建用户窗体位置的问题,一般不会引起多少后遗症,返回指示了该错误的代码就足够了。
Second, writing a throw statement does not mean it's time to create a new exception class. My recommendation on creating more rather than fewer Exception classes comes from normal human nature: People seem to gravitate to overusing System.Exception anytime they throw an exception. That provides the least amount of helpful information to the calling code. Instead, think through and create the necessary exceptions classes to enable calling code to understand the cause and provide the best chance of recovery.
第二,编写throw语句不意味着就要编写一个新的异常类。关于创建较多而不是较少异常类,我的建议来自正常的人类习惯:任何时候,当人们抛出异常时,会倾向于滥用System.Exception。那为调用代码提供了很少数量的有用信息。取而代之,要仔细考虑,创建必须的异常类,使调用代码能理解原因,提供最好的修复机会。
I'll say it again: The reason for different exception classes in fact, the only reason is to make it easier to take different actions when your users write catch handlers. Look for those error conditions that might be candidates for some kind of recovery action, and create specific exception classes to handle those actions. Can your application recover from missing files and directories? Can it recover from inadequate security privileges? What about missing network resources? Create new exception classes when you encounter errors that might lead to different actions and recovery mechanisms.
我将再说一遍:需要不同的异常类的原因,其实,唯一的原因就是,当你的用户编写catch子句的时候,容易采取不同的行为。查看这些错误情况,它们可能是一些修复措施的候选者,创建特定的异常类来处理这些动作。你的应用程序能够从丢失文件以及目录中恢复过来么?能够从不合适的安全权限里面恢复么?缺少网络资源了又怎么办?当你遇到可能导致不同行为以及恢复机制的错误时,请创建新的异常类。
So now you are creating your own exception classes. You do have very specific responsibilities when you create a new exception class. You should always derive your exception classes from the System.ApplicationException class, not the System.Exception class. You will rarely add capabilities to this base class. The purpose of different exception classes is to have the capability to differentiate the cause of errors in catch clauses.
因此,你现在就要创建自己的异常类。当你创建一个新的异常类的时候,有非常特殊的职责。应该总是从System.ApplicationException类派生自己的异常类,而不是System.Exception类。你几乎很少向这个基类添加任何功能。需要不同异常类的目的是,在catch子句中,对不同的原因有不同的处理能力。
But don't take anything away from the exception classes you create, either. The ApplicationException class contains four constructors:
但是也不要从你创建的异常类里面减少任何东西。ApplicationException类包含4个构造函数:
- // Default constructor
- public ApplicationException( );
- // Create with a message.
- public ApplicationException( string );
- // Create with a message and an inner exception.
- public ApplicationException( string, Exception );
- // Create from an input stream.
- protected ApplicationException( SerializationInfo, StreamingContext );
When you create a new exception class, create all four of these constructors. Different situations call for the different methods of constructing exceptions. You delegate the work to the base class implementation:
当你创建新的异常类的时候,不同的情况需要不同的构造异常的方法。你可以将该工作委托给基类的实现:
- public class MyAssemblyException : ApplicationException
- {
- public MyAssemblyException( ) : base( )
- {
- }
- public MyAssemblyException( string s ) : base( s )
- {
- }
- public MyAssemblyException( string s, Exception e) :base( s, e )
- {
- }
- protected MyAssemblyException( SerializationInfo info, StreamingContext cxt ) : base( info, cxt )
- {
- }
- }
The constructors that take an exception parameter deserve a bit more discussion. Sometimes, one of the libraries you use generates an exception. The code that called your library will get minimal information about the possible corrective actions when you simply pass on the exceptions from the utilities you use:
采用一个异常参数的那个构造函数需要更多一点的讨论。有时,你使用的众多库中的一个生成了异常。当你简单的传递出使用的信息时,调用你的库的代码将得到最少的关于修复的信息:
- public double DoSomeWork( )
- {
- // This might throw an exception defined in the third party library:
- return ThirdPartyLibrary.ImportantRoutine( );
- }
You should provide your own library's information when you generate the exception. Throw your own specific exception, and include the original exception as its InnerException property. You can provide as much extra information as you can generate:
在生成异常的时候,应该提供自己的库信息。抛出自己特定的异常,并将原来的异常作为InnerException属性包含在内。你就可以提供能够生成的最多的额外信息了:
- public double DoSomeWork( )
- {
- try {
- // This might throw an exception defined
- // in the third party library:
- return ThirdPartyLibrary.ImportantRoutine( );
- }
- catch( Exception e )
- {
- string msg = string.Format("Problem with {0} using library", this.ToString( ));
- throw new DoingSomeWorkException( msg, e );
- }
- }
- }
This new version creates more information at the point where the problem is generated. As long as you have created a proper ToString() method (see Item 5), you've created an exception that describes the complete state of the object that generated the problem. More than that, the inner exception shows the root cause of the problem: something in the third-party library you used.
在问题发生在哪里这一点上,新版本生成了更多的信息。只要你已经创建了合适的ToString()方法(见Item5),你就创建了一个这样的异常:描述了发生问题的对象的完整状态。不止这些,内部异样告诉你该问题的根本原因:你使用的第三方库里出了问题。
This technique is called exception translation, translating a low-level exception into a more high-level exception that provides more context about the error. The more information you generate when an error occurs, the easier it will be for users to diagnose and possibly correct the error. By creating your own exception types, you can translate low-level generic problems into specific exceptions that contain all the application-specific information that you need to fully diagnose and possibly correct the problem.
该技术叫做异常传递,将一个低层次的异常传递给高级别的异常,提供关于错误的更多的上下文。当异常发生时,你生成的信息越多,用户就能越早进行诊断并可能修正该错误。通过创建自己的异常类型,可以将低级别的一般问题转换成包含所有应用程序相关信息的特定异常,利用它们,你可以完全的进行诊断并可能修正该错误。
Your application will throw exceptions hopefully not often, but it will happen. If you don't do anything specific, your application will generate the default .NET Framework exceptions whenever something goes wrong in the methods you call on the core framework. Providing more detailed information will go a long way to enabling you and your users to diagnose and possibly correct errors in the field. You create different exception classes when different corrective actions are possible and only when different actions are possible. You create full-featured exception classes by providing all the constructors that the base exception class supports. You use the InnerException property to carry along all the error information generated by lower-level error conditions.
希望你的应用程序不会经常抛出异常,但是它确实会。不管什么时候,当你调用的核心框架内部的方法出现问题时,如果你不做些特别的,应用程序将生成默认的.Net框架异常。提供更多的细节信息,对于你和你的用户进行诊断并可能改正错误有很大的帮助。当且仅当不同的修正行为会被采用时,才创建不同的异常类。通过提供基本异常类支持的所有构造函数,你可以创建完全特定的异常类。使用InnerException属性来携带由低层次错误情况生成的错误信息。