什么是质量内建
随着时间的推移,我们项目的开发效率会逐渐降低,直到几年之后整个项目可能就无法维护,只能推倒重来。具体的表现首先就是随着时间推移,我们会发现整个需求列表里面能做的需求越来越少,因为每当我们增加一个新特性,需要改动的代码就非常多,所以最后每提出一个新的需求,团队评估出来的改动成本都非常高,导致最后难以增加新的特性。
第二个表现就是缺陷难以修复。我们做出来的系统只要有人用就会有反馈一些线上的故障,一开始代码很简单的时候修复起来是很快的,但是随着代码越来越复杂、代码行数越来越多,我们会发现定位问题太难了。尤其是现在我们的项目采用的是非常复杂的架构,所以当用户线上报错的时候,我们很难去定位到是哪里出了问题。但其实只要定位到了问题,修复起来是很快的。
第三个表现我们称之为“打地鼠现象”,简单来说就是当你“按”下一个缺陷的时候,又会蹦出来几个新的缺陷。这样会导致大家在工作的过程中压力非常大、心情也会比较沉重。
所以对于这些挑战,我们也有想办法去解决,CI、CD 以及 DevOps 的出现都让我们看到了很好的方向。但是我看到很多团队其实只是靠 DevOps 解决了一些基本的问题,并没有解决核心的问题。这是为什么呢?因为核心问题主要是靠开发人员的能力提升来解决的,但由于改变一个人是很难的,所以企业往往会绕开这些问题。所以我今天分享的内容主要会涉及到开发人员如何去写代码等一些实践。
我们在刚开始启动一个项目的时候,我们会制定一些代码规范,所以代码相对来说是比较清晰的。但是随着需求的演变,在实现这些需求的时候,每个人都会选择最低成本、最保险的方式。这就会导致没有人敢去大幅度地改动代码,只会在里面追加一些代码,造成了代码里面有大量的重复、过长的方法。同时开始的时候设计的架构也是非常清晰的,但是如果后续没有很好的落地、监控、自动化地发现问题,架构就会在这过程中腐化,变得一团乱。
Deming 先生曾提出“问题发现得越早,修复的成本越低”,这句话也是我们去降低软件开发成本、更高效地保证质量的重要原则。所以我们采用质量内建的方式,可以把整个软件质量的保障内嵌到开发的过程中去,而不是留到后面再去检测,因为越往后修复的成本越高。
85% 的缺陷都是在代码编码阶段引入的,然而大部分的缺陷并不是在编码的时候发现的,而是在后面的单元测试、功能测试、集成测试发现的,越往后发现的缺陷越多。按照刚刚那个原则,假如在编码阶段发现的缺陷只需要 1 分钟就能解决,那么单元测试阶段需要 4 分钟,功能测试阶段需要 10 分钟,而到了上线之后再发现可能就需要 640 分钟,这个成本是非常高的。
那么质量内建的方式是怎么样的呢?首先我们通过自动化测试、重构、简单设计等手段,可以使在编码阶段引入的缺陷变少,因为我们代码写清楚了,bug 就藏不住了。同时当我们做到自动化测试等工作时,在编码阶段发现的缺陷也变多了。那么通过质量内建,我们在编码阶段就把大部分的问题都捕获到,同时引入的缺陷也更少,它就降低了软件的开发成本。
大家可能会有一个疑惑,就开始开发人员原本只用写功能就行了,现在却还要写测试代码,而且测试代码的比例和实践代码的比例不一定,这样会不会增加成本。这里想跟大家说一下,很多人会把我们编写代码的时间当成整个软件开发的时间,其实不是。在编写玩代码之后,还得开发自测,然后还要去联调,之后还要进行内部测试、线上故障修复等,所以整个软件开发有这么多的过程,而我们现在解决问题的办法是在第一阶段投入更多的时间,做更多的测试、更多的代码优化等,从而减少后面所要花费的时间。根据刚刚说的修复时间越晚,成本越高的原则,我们这样做是划算的。
技术债与质量门禁
技术债是什么呢?债是一种比喻,与我们金融方面所说的债务意思相同,那么在我们技术范畴里面也有这样的债务问题。在我们编写代码的过程中留下了一些重复的代码,或者没有起好名字、没有给出注释,类似这样的问题就是我们欠下的技术债。
对比金融里的债务,技术债也有相应的特性。首先这个债我们必须得还,否则到了后面越欠越多可能会把整个团队压垮了,导致大家没有动力去开发新的功能。同时技术债是有利息的,假如最开始写代码的人留下了问题没有去解决,那么下一个接手这个代码的人可能就没法理解这个代码,就不敢大胆地去改代码,越晚就越不敢。
既然技术债存在这么多问题,大家为什么还要去欠债呢?因为技术债也有好处,有的时候我们要做的产品并不是一个非常靠谱的产品,我们就会追求更快,用一个比较粗糙的手段做完交给客户去进行测试。得到反馈之后,靠谱的话我们就会用心去优化、迭代;不靠谱的话我们就会放弃这个项目,这是它的成本也很低。所以由于互联网行业的这种快节奏,人们会倾向于欠下很多的技术债务,从而快速试错。当我得到反馈,确认用户的痛点之后再来进行代码的优化。当然我今天更想讲的我们为什么会欠下债务,其实还是是因为态度以及习惯问题。如果我们能改掉我们的坏习惯,我们就会少欠下一些技术债。
当我们搭建好一个项目的基础框架,写了一些示例的代码,后面就会上很多的人来做一些新需求。这个时候就会出现“失控”,我们会发现一开始的代码非常整洁,但是人一多之后就会形成“破窗效应”——简单来说就是一旦一扇窗户上出现了一个破洞,那么很快上面的破洞就会越来越多。代码库也是如此,当一个人没有按照规范写代码,同时没有人制止,那么很快其他人也会纷纷开始这样做,很快代码就会变得乱七八糟。
那么应对这种“破窗效应”的方法就是“童子军军规”,就是不管原来的质量怎么样,我们也得保证我们接手处理完之后,代码的质量要比原先好上一点、干净一点,哪怕是改一个变量命名也好,改一个格式也好,人人都这样做的话我们的代码库就会越来越干净、质量越来越高。这种方式就是我们所谓的“质量门禁”。
接下来讲一下偿还技术债,首先第一点是并非所有技术债都应偿还,或者说技术债的偿还应该有一个优先级,我们更应该关注的是那些频繁地需要变更的代码。第二点是应用童子军规则,也就是有债就还,不要拖欠太久,保证每次提交代码的时候比接手时要干净一点。第三点是先偿还高息技术债,就是看哪些问题不处理的话带来的后果会更严重,我们就优先处理这些问题。接着是分期偿还技术债,将我们的技术债管理起来,每次迭代的时候就一边做有客户价值的工作,一边偿还技术债。这里很关键的就是不能依赖开发人员的自觉性,而是在迭代的时候就要明确那哪一块要优化、要重构,分到个人的头上,同时后面要进行评测、验收,经过这样一个流程正式地去对待技术债。
自动化
自动化是一个实践,我们经常会听到像自动化发布、自动化打包、自动化构建、自动化测试等,尤其是自动化测试是一个反复被强调的一个实践。我们的流水线其实整个都是自动化的,构建是自动化的,检查是自动化的,包括后面的测试和部署也都是自动化的。
有一个原则叫自动化一切,就是“一切能被自动化的都应该被自动化”。除了常见的编译、检查、测试和部署,服务器的配置也可以进行自动化,甚至业务上的一些部署,比如一些迁移之类的,能自动化的我们都把它自动化。我们作为开发人员,最擅长的其实就是写代码,很多人会觉得自己的工作没什么挑战,这是因为你天天都在手工地做一些重复的事情,当然没有挑战了。这时候你可以尝试去自动化一些事情,你会发现很好玩,也能学到新的东西,个人能力能得到成长,同时做的事情也有价值。
我体会到自动化的几个好处,跟大家分享一下。第一个是沉淀知识,就是把知识沉淀到了自动化的脚本里面,而不是存在于某个人的脑子里。而对于掌握知识的这个人来说,他也减少了被打断的可能。第二点就是自动化能够提高效率,解放生产力,这一点其实是一个很明显的好处,原来手工要花五个小时的事情,自动化可能几分钟就跑完了。最后一点就是固化流程,降低出错率。也就是将我们的这个流程固化下来了,原本一件事情今天是 A 做,明天是 B 做,他们在做的时候可能就基于自己的理解来做,中间就会引入一些错误。而自动化就可以规避这种问题。
其他有效实践
①结对编程:结对编程是我非常推崇的实践,很多人认为结对编程就是一个人写一个人看,这样就浪费了一个人,其实不是的。其实结对编程有点像汽车拉力赛,领航员会看地图然后告诉 driver 前方的路线,例如前面的弯道该怎么走,所以他的视野会更加宏观,看得更远,也有助于对我们的 driver 做一个思维上的引导。写代码的时候也是这样的,操作键盘的人在考虑代码该怎么敲,而另一个人则是在引领思路,所以他俩是在互相配合的。
②代码评审:代码评审就是大家坐在一起,分享代码的收获、踩过的坑以及解决问题的方法、技巧。这是一个开发人员的交流活动,而不是一个类似于质量门禁的东西,这是有温度的一场交流、分享,传播有价值的东西。
③暴徒式编程:暴徒式编程是结对编程的一种方式,由一个人操作键盘,同时设置定时,每隔一段时间换人。其他人就负责盯着大屏幕告诉他该怎么操作,这个也是一个很好的学习手段。