如果你不是一个库开发者,在你的代码中不要过于防御
相反,写单元测试
事实上,即使你正在开发一个库,抛出异常大多数时候都是不好的。
1. 在c#中,不应该对int
类型的null
进行测试:
这会引发警告CS4072,因为它总是错误的。
2. 抛出异常意味着它是异常的:不正常和罕见的。
它不应该在生产代码中抛出。特别是因为异常堆栈遍历可能是一个占用CPU资源的任务。而且你永远不会确定异常将在哪里被捕获,如果它被捕获并记录或者只是简单地被忽略(在杀死其中一个后台线程之后),因为你无法控制用户代码。C#中没有像Java中的“checked exception”这样的机制,这意味着如果没有很好地记录,你永远不知道给定方法可能引发哪些异常。顺便说一下,这种文档必须与代码保持同步,这并不总是容易做到(增加维护成本)。
3. 异常会增加维护成本。
由于异常是在运行时和某些条件下抛出的,它们可能在开发过程中被检测得非常晚。正如你可能已经知道的那样,在开发过程中越晚发现错误,修复的代价就越高。我甚至见过异常抛出的代码进入了生产代码,并且在一周内没有抛出异常,然后每天都抛出异常(导致生产环境崩溃。糟糕!)。
4. 在无效输入上抛出异常意味着你无法控制输入。
这是关于库的公共方法的情况。然而,如果您可以使用另一种类型(例如非空类型,如int)在编译时检查它,那么这就是应该采取的方法。当然,由于它们是公共的,它们有责任检查输入。
想象一下使用他认为是有效数据的用户,然后由于副作用,堆栈跟踪中的一个方法抛出了 ArgumentNullException。
他会有什么反应?
他如何应对?
您能轻松提供解释信息吗?
5. 私有和内部方法绝不能抛出与其输入相关的异常。
您可以在代码中抛出异常,因为外部组件(可能是数据库、文件或其他)正在出现问题,您无法保证您的库将在其当前状态下继续正确运行。
将一个方法设置为public并不意味着它应该(仅仅是可以)从你的库之外被调用(请参考Martin Fowler的Public versus Published)。使用IOC、接口、工厂,只向用户发布必要的内容,同时让整个库类可用于单元测试。(或者您可以使用InternalsVisibleTo
机制)。
6. 抛出没有任何解释信息的异常是在嘲笑用户
当一个工具坏了,却没有任何修复的线索,人们会有什么感受,无需提醒。是的,我知道。你来到SO并提出问题......
7. 无效的输入意味着代码出错
如果你的代码可以使用这个值产生有效的输出,那么它就不是无效的,你的代码应该管理它。添加一个单元测试来测试这个值。
8. 以用户为中心思考:
你喜欢使用的库抛出异常打破你的脸吗?像:“嘿,这是无效的,你应该知道!”
即使从您的角度来看 - 根据您对库内部的了解,输入无效,您如何向用户解释(友善和礼貌):
- 清晰的文档(Xml文档和架构概述可能有所帮助)。
- 发布库的xml文档。
- 如果有异常,请提供清晰的错误说明。
- 给予选择:
看看字典类,您更喜欢什么?您认为哪个调用速度最快?哪个调用可能会引发异常?
Dictionary<string, string> dictionary = new Dictionary<string, string>();
string res;
dictionary.TryGetValue("key", out res);
或者
var other = dictionary["key"];
9. 为什么不使用Code Contracts?
这是一种避免丑陋的if then throw语句并将合同与实现隔离开来的优雅方式,允许同时为不同的实现重复使用合同。您甚至可以向库用户发布合同,以进一步解释如何使用该库。
总之,即使您可以轻松使用throw,即使您在使用.Net Framework时可能会遇到异常,也并不意味着可以毫无顾虑地使用它。