客户端敏捷开发之道【四】:防御式开发
发表于2016-12-15
本来这个话题是不想拿出来讲的,因为这是我工作到第三年才总结出来的开发方法,不太成熟。但是最近心情不太好,想写几篇文章稳定一下情绪,各位看官如果觉得写得还不错,就点个赞安慰下我吧。
一、0 BUG的梦想
刚刚工作时,有一次封闭开发,代码逻辑反复检查调试,确认没有问题,就在小房间里说这次提测质量很高,估计要0BUG。之后没想到测试出来我开发的这个模块的BUG是最多的,因而0BUG被其他码畜兄弟嘲笑了好几个月。
实际这是个值得被反复思考的问题,为什么我们的提测总是做不到0 BUG。在那次提测中,我新开发的模块确实非常稳定,没有BUG。但是对产品需求理解错误、其它模块出现问题影响、遗留问题等等状况,直接导致这次提测表现出非常多的BUG。
作为客户端开发,我们时常能感觉到自己处于需求迭代洪流的中心位置。后台数据错误,表现在客户端里,因此BUG单会直接提给你。产品表现异常、其他模块影响、设计效果不理想,这些统统需要经由作为直接表现层的客户端开发确认问题后再行处理。
有时我们发现,单纯的提高写代码的技艺并不能成为一个更靠谱的程序员,原因是:我们的开发过程是一个反复叠加的协作过程,而单纯的程序开发,只是其中小小的一环。
二、万物皆为接口
协作的过程理论上来说是模块化的过程,而信任是分工协作的前提。当我们乘坐一架飞机时,我们只需要在接口处(售票处)买一张机票就可以,无需亲自检查飞机的各项指标是否正常,我们信任航空公司可以搞定一切。
而开发过程则是一个不可信的过程,当我们的需求敲定时,设计稿敲定时,当后台接口稳定时,当其他模块不会出BUG时,通常你就会投入到下一项工作的开发中。所以说,开发过程实际上是在接口不可信的情况下进行分工协作的过程。
三、防御式开发
有了上面的铺垫,我们就要正式介绍防御式开发了:防御式开发是指在团队中将整个开发过程定义成若干协作模块,在我们自己负责的模块的入口和出口设计安全校验、诊断机制、容错机制。以达到出现问题快速排查定位和容错的处置方法。
四、防御式开发:编程
这一节,我们简单介绍一些常见的编程过程中的防御实践。如果把函数、类、模块的调用全部都看成协作,函数的调用是与其他函数协作,类的调用是与其他类协作,模块的调用是与其他模块协作,那么接下来的思路就顺理成章可以推演下去了。
1、函数防御
在上面的例子中funInner的判空是否有必要,在鹅厂,我见过两种针锋相对的观点。认为没有必要的观点是:1、浪费性能;2、增加安装包大小;3、在调用处已然做了判空。
然而在防御式开发的理念来看,不信任是主基调。无法确认在未来迭代的过程中funInner是否会被其它新增的函数调用,也无法判断新增函数是否有判空。因此函数使用前一律对函数参数进行判空(如果是C++则全部指针都要进行判空)。
在实践过程中,碰到过大量类似案例,由于需求迭代,新增函数调用导致的crash。在防御式开发理念来看:判空的性能不是问题,安装包大小不是问题,人脑check的成本是问题,只要记住逢外部数据一律判空不信任这一条,形成习惯后会惊讶的发现自己的代码靠谱多了。
2、类防御
前段时间我们团队出现过一起差点导致延期发布的BUG,原因是新人在使用完数据库时将数据库关闭,而此处的潜规则是,数据库一律不能关闭。
从防御式开发的角度来看,不信任调用者是主基调。对于一个类来说,应当准许调用者以任意顺序、任意时机、任意线程调用。当确要以一定顺序调用类时,应当通过命名等手段尽量告知调用者。被调用后,应当通过LOG打印诊断信息,甚至抛出异常,确保第一时间定位问题。
3、模块防御
一般来说对外调用的接口必须严格遵守类防御的规则,除此之外模块防御还要保证其它模块的崩坏不会影响到自身模块。如果是JAVA开发,则需要在入口和出口处增加LOG,在出现问题时快速定位问题所在模块。如果是C++开发,还需要对于极度重要的内存区域进行保护,可以通过修改内存属性确保其它模块的指针调用不会擦枪走火改动到自己(一般模块不需要做到这个程度)。
五、防御式开发:协作
1、测试防御
在和测试协作过程中,需求描述以及BUG反馈是主要的“交互”。BUG反馈和接口调用其实非常类似,需要先“判空”,即确认该BUG是否是由于环境问题导致的、测试操作是否正确。而反馈给测试的需求则需要尽量清晰详实,就像我们提供接口给外部调用一样。
2、产品防御
在和产品经理协作的过程中,你们懂的。反复确认需求其实和接口判空是一个道理。在开发的过程中,对于可能会发生变化的需求做好预判,关于这一点,可以参考第一篇分享,这里不再赘述。
整体来说,防御式开发是一种思维方式,将具体问题抽象成接口模型,然后针对不信任的入口出口设计应对方案,以达到快速定位和解决问题的目的。由于篇幅有限,以及这套思维方法还在验证中,如有不合理之处,欢迎指教。