哪个网站diy做宝宝衣服,ec2 wordpress,中国交通建设股份有限公司官网,网站建设首先异常是使用.NET时必然会遇到的问题#xff0c;但是#xff0c;有太多的开发人员没有从API设计的角度考虑这个问题。在大部分工作中#xff0c;他们自始至终都知道需要捕获什么异常以及哪些异常需要写入全局日志。如果你设计了可以让你正确使用异常的API#xff0c;则可以显… 异常是使用.NET时必然会遇到的问题但是有太多的开发人员没有从API设计的角度考虑这个问题。在大部分工作中他们自始至终都知道需要捕获什么异常以及哪些异常需要写入全局日志。如果你设计了可以让你正确使用异常的API则可以显著减少修复缺陷的时间。 谁的错 异常设计背后的基本理论始于这样一个问题“谁的错”为了方便本文的讨论这个问题的答案将总是以下三者之一 库应用程序环境 当我们说“库”有问题我们是指当前执行的某个方法有内部缺陷。在这种情况下“应用程序”是调用库方法的代码这有点混杂难分因为库和应用程序代码可能在相同的程序集中。最后“环境”是指应用程序之外一切无法控制的东西。 库缺陷 最典型的库缺陷是NullReferenceException。对库而言它没有任何理由抛出可以被应用程序检测到的空引用异常。如果遇到了空则库代码应该总是抛出一个更具体的异常说明什么为空以及如何纠正这个问题。对于参数而言这显然是一个ArgumentNullException异常。而如果属性或字段为空则InvalidOperationException通常更合适。 根据定义任何表明库缺陷的异常都是该库中需要修复的Bug。那并不是说应用程序代码没有Bug而是说库的Bug需要首先修复。只有那样才能让应用程序开发人员知道他也犯了错误。 这样做的原因是可能有许多人使用同样的库。如果一个人在不应该传入空的地方错误地传入了空则其他人想必也会犯同样的错误。把NullReferenceException替换为一个可以清晰地显示出什么出错的异常应用程序开发人员立即就可以知道什么出错了。 “成功之核The Pit of Success” 如果你读过有关.NET设计模式的早期文献那么你会经常碰到短语“成功之核”。其基本思想是这样的让代码容易被正确使用不容易被误用并确保异常可以告诉你哪里出错了。遵循这个API设计理念几乎可以保证开发人员一开始就编写出正确的代码。 这就是为什么一个没有注释的NullReferenceException是如此糟糕。除了堆栈跟踪外可能非常深入库代码没有任何信息可以帮助开发人员确定他们哪里做错了。另一方面ArgumentNullException和InvalidOperationException则为库作者提供了一种方法让他们可以向应用程序开发人员说明如何修复问题。 其他库缺陷 下一个库缺陷是ArithmeticException系列包括DivideByZeroException、FiniteNumberException和OverflowException。再次这总是意味着库方法的内部缺陷即使那个缺陷只是一个缺失的参数有效性检查。 库缺陷的另外一个例子是IndexOutOfRangeException。从语义上讲它和ArgumentOutOfRangeException没什么不同参见IList.Item但它只适用于数组索引器。由于应用程序代码通常不会使用裸数组所以这意味着自定义的集合类会有Bug。 自.NET 2.0引入泛型列表以来ArrayTypeMismatchException就很少见了。触发该异常的情况相当怪异。根据文档 当系统无法将数组元素转换成声明的数组类型时会抛出ArrayTypeMismatchException。例如一个String类型的元素无法存入一个Int32数组因为这两种类型之间无法转换。应用程序一般是不需要抛出这类异常的。 要做到这一点前面提到的Int32数组必须存入一个Object[]类型的变量。如果你使用了原始数组则库需要对此进行检查。由于这个原因及其他许多方面的考虑最好是不要使用原始数组而是将它们封装到一个合适的集合类中。 通常其他转换问题是通过InvalidCastException异常反映出来的。回到我们的主题类型检查应该意味着永远不会抛出InvalidCastException异常而是向调用者抛出ArgumentException或InvalidOperationException异常。 MemberAccessException是一个基类涵盖了各种基于反射的错误。除了直接使用反射外COM互操作和动态关键词的不正确使用都会触发该异常。 应用程序缺陷 典型的应用程序缺陷是ArgumentException及其子类ArgumentNullException和ArgumentOutOfRangeException。以下是其他你可能不知道的子类 System.ComponentModel.InvalidAsynchronousStateExceptionSystem.ComponentModel.InvalidEnumArgumentExceptionSystem.DuplicateWaitObjectExceptionSystem.Globalization.CultureNotFoundExceptionSystem.IO.Log.ReservationNotFoundExceptionSystem.Text.DecoderFallbackExceptionSystem.Text.EncoderFallbackException 所有这些都明确地表明应用程序有错误而问题就出在调用库方法的行里。那条语句的两个部分都很重要。考虑下面的代码 foo.Customer null; foo.Save(); 如果上述代码抛出了一个ArgumentNullException异常那么应用程序开发人员会很困惑。它应该抛出一个InvalidOperationException异常说明当前行之前有什么地方出了问题。 以异常为文档 典型的程序员不阅读文档至少不会首先阅读文档。相反他或她会阅读公共API编写一些代码并运行。如果代码不能正常运行就到Stack Overflow上搜索异常信息。如果该程序员够幸运则很容易在那里找到答案以及指向正确文档的链接。但即使如此程序员们很可能也不会真正地读它。 那么作为库作者我们如何解决这个问题第一步是直接将部分文档复制到异常中。 更多对象状态异常 InvalidOperationException有一个众所周知的子类ObjectDisposedException。它的用途显而易见然而很少有可销毁类会忘记抛出这个异常。如果忘记了则常见的结果是抛出NullReferenceException异常。该异常是由Dispose方法将可销毁子对象置为空所导致的。 与InvalidOperationException密切相关的是NotSupportedException异常。这两种异常很容易区分InvalidOperationException是指“你现在不能那样操作”而NotSupportedException是指“你永远不能对这个类做那种操作”。理论上讲NotSupportedException应该只在使用抽象接口时出现。 例如一个不可变集合在遇到IList.Add方法时应该抛出NotSupportedException异常。相比之下一个可冻结集合在冻结状态下遇到该方法时会抛出InvalidOperationException异常。 NotSupportedException一个越来越重要的子类是PlatformNotSupportedException。该异常表示操作可以在某些运行环境里进行但不能在其他环境里进行。例如当将代码从.NET移植到UWP或.NET Core时你可能需要使用这个异常因为它们没有提供.NET Framework的所有特性。 难以捉摸的FormatException 微软在设计.NET的第一个版本时犯了一些错误。例如从逻辑上讲FormatException是一个参数异常类型甚至文档也说“该异常是在参数格式无效时抛出”。但是不管出于什么原因它实际上没有继承ArgumentException。它也没有地方存放参数名称。 我们暂时提供的建议是不要抛出FormatException异常而是自己创建ArgumentException的子类可以命名为“ArgumentFormatException”或其他效果类似的名称。这可以为你提供必要的信息如参数名称和实际使用的值减少调试时间。 这把我们带回了最初的主题“异常设计”。是的当你自行开发的解析器检测到了问题你可以只抛出一个FormatException异常但那无法为想要使用你的库的应用程序开发人员提供帮助。 有关这个框架设计缺陷另外一个例子是IndexOutOfRangeException。从语义上讲它和ArgumentOutOfRangeException没什么不同然而这个特例只是针对数组索引器吗不那样想就错了。看下IList.Item的实例集该方法只会抛出ArgumentOutOfRangeException异常。 环境缺陷 环境缺陷源于世界并不完美这样一个事实诸如数据宕机、Web服务器无响应、文件丢失等场景。当Bug报告中出现环境缺陷时需要考虑以下两个方面 应用程序正确地处理了缺陷吗在这个环境里是什么导致了缺陷 通常这会涉及人员分工。首先应用程序开发人员应该第一个查找问题的答案。这不仅仅是说要处理错误并恢复而且要生成一个有用的日志。 你可能想知道为什么要从应用程序开发人员开始。应用程序开发人员要对运维团队负责。如果一次Web服务器调用失败则应用程序开发人员不能只是甩手大叫“不是我的问题”。他或她首先需要确保异常提供了足够的细节信息让运维人员可以开展他们的工作。如果异常仅仅提供了“服务器连接超时”的信息那么他们怎么能知道涉及了哪台服务器 专用异常 NotImplementedException NotImplementedException表示且仅表示一件事这项特性还在开发过程中。因此NotImplementedException提供的信息应该总是包含一个任务跟踪软件的引用。例如 throw new NotImplementedException(参见工单#42.); 你可以提供更详细的信息但实际上你记录的任何信息几乎立刻就会过期。因此最好是只将读者导向工单他们可以在那里看到诸如该特性按计划将会在何时实现这样的信息。 AggregateException AggregateException是必要之恶但很难使用。它本身不包含任何有价值的信息所有的细节信息都隐藏在它的InnerExceptions集合中。 由于AggregateException通常只包含一个项所以在库中将它解封装并返回真正的异常似乎是合乎逻辑的。一般来说你不能在没有销毁原始堆栈跟踪的情况下再次抛出一个内部异常但从.NET 4.5开始该框架提供了使用ExceptionDispatchInfo的方法。 解封装AggregateException catch (AggregateException ex) { if (ex.InnerExceptions.Count 1) //解封装 ExceptionDispatchInfo.Capture(ex.InnerExceptions[0]).Throw(); else throw; //我们真的需要AggregateException } 无法回答的情况 有一些异常无法简单地纳入这个主题。例如AccessViolationException表示读取非托管内存时有问题。对那可能是由原生库代码所导致的也可能是由应用程序错误地使用了同样的代码库所导致的。只有通过研究才能揭示这个Bug的本质。 如果可能你就应该在设计时避免无法回答的异常。在某些情况下Visual Studio的静态代码分析器甚至可以分析该规则所涵盖的标识冲突。 例如ApplicationException实际上已经废弃。Framework设计指南明确指出“不要抛出或继承ApplicationException。”为此应用程序不必抛出ApplicationException异常。虽说初衷如此但看下下面这些子类 Microsoft.JScript.BreakOutOfFinallyMicrosoft.JScript.ContinueOutOfFinallyMicrosoft.JScript.JScriptExceptionMicrosoft.JScript.NoContextExceptionMicrosoft.JScript.ReturnOutOfFinallySystem.Reflection.InvalidFilterCriteriaExceptionSystem.Reflection.TargetExceptionSystem.Reflection.TargetInvocationExceptionSystem.Reflection.TargetParameterCountExceptionSystem.Threading.WaitHandleCannotBeOpenedException 显然这些子类中有一些应该是参数异常而其他的则表示环境问题。它们全都不是“应用程序异常”因为他们只会被.NET Framework的库抛出。 同样的道理开发人员不应该直接使用SystemException。同ApplicationException一样SystemException的子类也是各不相同包括ArgumentException、NullReferenceException和AccessViolationException。微软甚至建议忘掉SystemException的存在而只使用其子类。 无法回答的情况有一个子类别就是基础设施异常。我们已经看过AccessViolationException以下是其他的基础设施异常 CannotUnloadAppDomainExceptionBadImageFormatExceptionDataMisalignedExceptionTypeLoadExceptionTypeUnloadedException 这些异常通常很难诊断可能会揭示出库或调用它的代码中存在的难以理解的Bug。因此和ApplicationException不同把它们归为无法回答的情况是合理的。 实践重新设计SqlException 请记住这些原则让我们看下SqlException。除了网络错误你根本无法到达服务器外在SQL Server的master.dbo.sysmessages表中有超过11000个不同的错误代码。因此虽然该异常包含了你需要的所有底层信息但是除了简单地捕获记录外你实际上难以做任何事。 如果我们要重新设计SqlException那么我们会希望根据我们期望用户或开发人员做什么将其分解成多个不同的类别。 SqlClient.NetworkException会表示所有说明数据库服务器本身之外的环境存在问题的错误代码。 SqlClient.InternalException会包含说明服务器存在严重故障如数据库损坏或无法访问硬盘的错误代码。 SqlClient.SyntaxException相当于我们的ArgumentException。它是指你向服务器传递了糟糕的SQL直接或者因为ORM的Bug。 SqlClient.MissingObjectException会在语法正确但数据库对象表、视图、存储过程等不存在时出现。 SqlClient.DeadlockException出现在两个或多个进程试图修改相同的信息产生冲突时。 这些异常中的每一种都隐含着一个行动方案。 SqlClient.NetworkException重试操作。如果频繁出现则请联系运维人员。SqlClient.InternalException立即联系DBA。SqlClient.SyntaxException通知应用程序或数据库开发人员。SqlClient.MissingObjectException请运维人员检查上一次数据库部署是否丢了东西。SqlClient.DeadlockException重试操作。如果频繁发生则查找设计错误。 如果要在实际的工作中这样做那么我们必须将所有11000多个SQL Server错误代码映射到那些类别中的一个这是一项特别令人望而生畏的工作这也就解释了为什么SqlException是现在这个样子。 总结 当设计API时为了便于纠正问题要将异常根据需要执行的动作的类型进行组织。这样更容易编写出自校代码记录更准确的的日志更快地将问题传达给合适的人或团队。 关于作者 Jonathan Allen在90年代末开始参与面向医务室的MIS项目把它们从Access和Excel逐步提升为一种企业级的解决方案。他花了五年时间编写金融行业自动交易系统然后决定转向高端用户界面开发。在业余时间里他喜欢学习15到17世纪之间的西方格斗技巧并进行相关写作。 原文地址http://www.infoq.com/cn/articles/Exceptions-API-Design .NET社区新闻深度好文微信中搜索dotNET跨平台或扫描二维码关注