1. 防御式编程
1.1 墨菲定律
墨菲定律:如果有两种或两种以上的方式去做某件事情,而其中一种选择方式将导致灾难,则必定有人会做出这种选择。
墨菲定律主要内容:
- 任何事都没有表面看起来那么简单;
- 所有的事都会比你预计的时间长;
- 会出错的事总会出错;
- 如果你担心某种情况发生,那么它就更有可能发生。
1.2 防御式编程概念
防御式编程:子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据。
防御式编程和防御式驾驶
防御式编程,这一概念来自防御式驾驶。在防御式驾驶中要建立这样一种思维,那就是你永远也不能确定另一位司机将要做什么。这样才能确保在其他人做出危险动作时你也不会受到伤害。你要承担起保护自己的责任,哪怕是其他司机犯的错误。
1.3 防御哪些地方
- 边界防御:检查所有的外部输入
在防御式编程的理念中,所有的外部输入都是不可信的,需要校验是否在可允许的范围内。这里需要检查的项包括,空指针、数组越界、不合法入参等。特别是当我们在写一个公共方法时,不确定这个方法会在未来某个时刻,被某个外部系统调用,做好输入检查既能保护自身程序运行的健壮性,又可以让外部系统放心调用。 - 异常处理:在正确性和健壮性之间做好取舍
正确性:程序永不返回不准确的结果,即使这样做会不返回结果或是直接退出程序。
健壮性:系统在不正常的输入或不正常的外部环境下仍能正常运行,哪怕输出结果是错误的或者不完整的。
正确性和健壮性往往是相互矛盾的,当我们检查出错误数据后,还需要决定如何处理它。防御性编程不会掩盖错误,也不会隐藏bug。这需要在健壮性和正确性之间做权衡。 - 应检尽检:没有完全可靠的外部环境
我们在coding时,会有很多外部方法的调用和交互,要对所有的外部调用保持警惕。这些API或者三方类库也是人写的,人写的就意味着可能有bug。一种好的思路是尽可能地按照自身逻辑,对外部调用做检查和异常处理(exception handle)。 - 显示约束:简单直接的代码风格
在防御式编程中,我们提倡使用“最笨”的方式写代码,尽量少的使用一些语法糖或者隐性规约。 - 减少依赖:write once, run anywhere
"write once, run anywhere"是Sun公司用来展示Java程序设计语言的跨平台特性的口号,这本身就是一种防御式编程的理念体现,即在代码中减少对环境的依赖,确保外界环境改变了,程序依然可以正常运行。 - 傻瓜式注释
代码是给系统看的,注释是给人看的。要想代码具有比较好的可阅读性,应该把自己当作傻子去添加注释。(这个和clean code 的理念不同,clean code提倡代码即注释,个人认为这是一种理想化的情境) - 避免过度设计
避免预防不可能会发生的错误,过多的防御式代码也会导致程序显得臃肿、难以维护,同时程序的性能也会受到影响。
1.4 总结
防御式编程是一种安全的编程思想,本质上是要求开发人员对代码和线上环境报以辩证的态度和敬畏之心。它通过以下途径,从而来提升系统健壮性:
-
提高工程质量——减少bug和问题;
-
提高源码可读性—— 源码应该变得可读且可理解,并且能经受code review;
-
让软件能通过预期的行为来处理不可预期的用户操作。
作为一名优秀的开发者,不能将希望完全寄托于测试,测试驱动开发,而是在设计、开发阶段,对系统的异常和边界有充分的认知和考量,这是防御式编程带给我们的思考。
2. 契约式编程
2.1 合同描述
在人类的社会活动中,契约一般是用于两方,一方(供应者)为另一方(客户)完成一些任务。每一方都期待从契约中获得利益,同时也要接受一些义务。通常,一方视为义务的对另一方来说是权利。契约文档要清楚地写明双方的权利与义务。契约合同能保障双方的利益,对客户来说,合同规定了供应者要做的工作;对供应者来说,合同说明了如果约定的条件不满足,供应者没有义务一定要完成规定的任务。
2.2 契约式编程概念
契约的特点:
- 契约关系的双方是平等的,对整个bussiness的顺利进行负有共同责任,没有哪一方可以只享有权利而不承担义务。
- 契约关系经常是相互的,权利和义务之间往往是互相捆绑在一起的;
- 执行契约的义务在我,而核查契约的权力在对方;
- 我的义务保障的是你的利益,而你的义务保障的是我的利益;
2.3 DEMO理解
if (dest == NULL) { ... }
这就是义务,其要点在于,一旦条件不满足,我方(义务方)必须负责以合适手法处理这尴尬局面,或者返回错误值,或者抛出异常。
assert (dest != NULL);
这是检查契约,履行权利。如果条件不满足,那么错误在对方而不在我,我可以立刻“撕毁合同”,罢工了事,无需做任何多余动作。
2.4 总结
我们以往对待“过程”或“函数”的理解是:完成某个计算任务的过程,这一看法只强调了其目标,没有强调其条件。在这种理解下,我们对于exception的理解非常模糊和宽泛:只要是无法完成这个计算过程,均可被视为异常,也不管是我自己的原因,还是其他人的原因(典型的权责不清)。
- 报告:其他模块未能履行契约,本过程无需作出任何反应,仅需告知对方。
- 异常:对方完全满足了契约,而我依然未能如约完成任务。