事件背景始于数十亿年前的一次行星大爆炸。对不起,我背错台词了。说说一个月前讨论的课件的局部数据结构,简单来说就是在约定的内容中遇到异常,是在程序内部处理还是抛出。比如我们要解析一段json,约定这个json的格式只能是正常格式或者为空,那么一旦返回json的方法返回了一个既不是正常格式也不是空的异常值,程序如何处理?小华:一旦遇到协议异常,程序一定要兼容,程序不能死机,相信只要是程序员,基本都遇到过。举个最常见的栗子,NullPointerException。如果我们想从json中取一个字段,突然发现出现了NullPointerException。一些开发人员认为这是数据问题。然后把json里的这个字段改对就行了;有开发者认为是程序问题,认为程序在使用前需要进行非空判断。我相信这两类程序员各有各的道理。第一种程序简洁明了,代码逻辑干净,但一旦出错就会崩溃。第二种方案是持久的。crash,但是代码中到处都是非空判断,臃肿重复。存在还是不存在,这是一个问题!Defensiveprogramming在我们争论这个问题的时候,突然有一个叫Kang的同事施法,祭出一块砖头(《代码大全2》,近900页,相当于3本书《Android群英传》),我一度以为他要打我脸。正当我要躲闪的时候,他翻到了这块砖头的第八章,几个大字印在了我的视线里——“防御式编程”。果然是老司机,却能从防御性驾驶中实现防御性编程。如果他说好的编程不会开车,开车不会编程怎么办?不知道这位作者是不是擅长编程,但我知道,说到驱动,他一定不是何钓君!OK,《代码大全》给了我们一个定义——“防御性编程”,说白了就是“人类是不安全的,不值得信任的,所有人都会犯错误,而你写代码应该考虑到所有可能的错误,所以你的程序不会因为别人的错误而出错。”在书中,作者告诉我们程序需要兼容可能的错误输入,比如一个除法函数,你必须判断分母可能为0的情况,以便向调用者返回错误信息。另外,一般的高级编程语言都提供了“断言”和“异常”两种错误处理方式。Assertion断言是在开发阶段使用的代码,让程序在运行时进行自检。如果断言为真,程序正常运行,如果断言为假,程序异常退出。等等,防御性编程不是说要兼容异常吗?为什么退出了?其实作者的意思是先断言再处理错误,断言是在开发环境中。正式推出后,将不再有任何断言。但实际上,这是一个悖论。开发阶段的错误处理代码被开发阶段的断言拦截,但是错误处理代码也是人写的,那么如何检测“错误处理代码中可能出现的错误”呢?Exception当代码出现问题时,可以通过抛出异常来通知它。如果自己处理不了,可以交给外界处理。这个就不多说了,毕竟大部分的代码,如果出现异常,最简单的就是trycatch,我什至看过所有的代码直接trycatch,你多么不相信人类。所以我想,防御性编程用久了,我是不是会开始怀疑自己的人生了?果然后来翻了几页,作者也给了建议。借用奇异博士的一句话——“你他妈的在咒语的下一页写了警告”!简而言之,防御性编程就是用怀疑的眼光看待所有的代码,但是这个话题还是和我们稍微讨论了一下。有差异。我们讨论的话题是“已经有合约,但合约外的内容被退回”。契约编程就在我们讨论的时候,天空突然飘来了五个字——那不是问题,哦不,是“契约编程”。这个好像有点像!我们先简单了解一下什么是合约编程。简单地说,合同作用于双方,每一方都会完成一些任务以促成合同的订立,但同时,每一方也会接受一些义务,作为订立合同的前提条件,如果任何一方无视履行义务的义务,合同将失效。契约式编程要求我们在“前提条件”、“后续条件”和“不变条件”中检查契约。类似的,比如检查参数,一旦参数不对,合约会立即被撕毁。这一点现在很多新的语言都支持了,比如Swift,它支持对参数进行约束检查,这是一种类契约编程。合同约束的是“保证程序正常运行的条件”。一旦合约损坏,只有一个原因,那就是程序有bug。比如一个数据字段,我处理的时候要保证它不为空。是的,那谁来保证这个,肯定是我的调用者(或者其他模块),所以如果有问题,应该有调用者来检查,保证调用的时候一定不能为空。这让我想起了我刚开始为日语编程时的一些事情。日本人的做事风格是出了名的谨慎和细致。每个方法和函数都设计有参数和返回值,它们的类型和所有可能的取值都已经设计好了,每个方法之间都有明确的界限。如果你的方法因为传入的参数不在设计范围内而导致错误,你可以去找调用者。他需要按设计拨打电话。不得不说,这应该是合约编程的最佳实践。日本企业普遍采用这种方式其实还有一个原因,就是可以严格区分责任,让大家不必为了迁就别人的错误而进行“困难的编码”。每个人都按照约定处理自己的事情,让毁约者承担责任。再延伸一下,这很像现在的“面向接口编程”。两个模块之间,定义了调用和处理的接口,具体实现,对方不需要关心,只要安装协议的接口进行开发即可。可以,但是光有接口是不够的,还需要契约做进一步的约束,比如对参数和返回值的约束。无独有偶,在《代码大全》中,作者也提出了“进攻性编程”,其实和合约编程类似。乌托邦还好,从梦中醒来,让阳光照进现实。以上两种编程方式都是非常理想化的编程,但是在一般公司,无论是防御还是合同,都很难实现,比如前后端的接口,不同部门同事之间的沟通。编程,没人关心你的契约,按照防御性编程,代码很恐怖,很容易漏防。那我们该怎么办呢?我觉得如果能在公司层面推广契约式编程,首先是提高开发效率,让每个人对自己写的代码负责,在开发者之间建立良好的信任关系。同时,也可以减少不必要的沟通成本和精力。但同时,必要的防御性编程也是必不可少的,这是保证程序健壮性和稳定性的前提。怎么说呢,中国人传承了几千年的传统——“中庸之道”,契约还是防御,视情况而定,这就是平衡的艺术。警告请大家防御性阅读本文,大家有错请留言交流。此文一出,极有可能引发双方混战,红与黑,天灾or守卫,联盟or部落,选你那一边。
