转载:http://blog.itpub.net/69978212/viewspace-2702938
一、什么是质量内建
1.1 关于质量
在软件开发里面,质量是个永恒的话题,如下图所示,这是传统项目管理中的项目管理三角形:我们可以发现三角形的三条边成本,时间和范围所围绕着的是质量,也就是说 质量被认为是必选的中心,不可以妥协的一个属性。
![](http://img.blog.itpub.net/blog/2020/07/07/ad6454f027e3a89c.jpeg?x-oss-process=style/bb)
![](http://img.blog.itpub.net/blog/2020/07/07/b503071dea2b85ed.jpeg?x-oss-process=style/bb)
1.3 精益敏捷中的质量内建
质量内建是精益核心原则之一,它有助于我们减少浪费,有助于避免与需求召回、返工及缺陷修复相关的延迟成本。 如下图所示,我们可以知道:有大约85%的bug发生在coding阶段,但是解决他们的代价是最低的。也就是说,如果我们能够在coding阶段就避免掉这些bug,那么我们的产品里面就会减少85%的bug,从而避免了后面解决缺陷带来的指数增长的成本。也就是 “问题发现的越早,修复的成本越低”。![](http://img.blog.itpub.net/blog/2020/07/07/4959f327cac25cb6.jpeg?x-oss-process=style/bb)
1.4 SAFe里的内建质量
内建质量是SAFe的四个核心价值观之一。![](http://img.blog.itpub.net/blog/2020/07/07/8973a0bc9dee180e.jpeg?x-oss-process=style/bb)
二、如果没有质量内建
如果没有质量内建,那么我们遇到的将会是- 不断增长的技术债,直到无力偿还。
- 开发新feature与修改旧bug的摩擦。
- 无法预期的交付,对于客户响应变慢。
- 团队对于质量丧失了信心。
2.1 技术债
技术债,也就是我们在代码里留下的坏味道。比如一个类的代码行数太多、编码格式不符合编码规范、重复代码、hardcode、workaroud、架构设计不合理等等。需要说明的是技术债并不总是坏事,或者说技术债并不是零容忍的。比如说在我们一个产品的设计初期,可能还处于跑通商业的阶段,那么需要我们非常迅速的上线我们的设想,并得到客户的验证,这时候为了快速试错,我们会不可避免地引入技术债。但是 需要注意的是一旦得到反馈并决定继续的时候,在接下来的迭代需要及时偿还技术债务,以避免技术债的累积和蔓延。技术债是有利息的。 程序员在写代码的时候,如果发现原有的代码很糟糕,心底里会一边骂一边不由自主地拷贝粘贴,如法炮制出同样风格的烂代码。于是,在代码走查和故障复盘的时候,我们听到最多的辩解是:原来的代码就是这么写的……质量差的代码不停地增加技术债务,应该变被动为主动,只有内建质量才能进入良性循环。技术债是躲不掉的,现在不还技术债只会让我们在将来付出更大的代价去偿还。为什么需要第一时间解决软件里的缺陷?就是因为你不能扩展糟糕的代码,从而避免技术债的利息。2.2 新特性和旧缺陷的摩擦
在敏捷开发里面我们承诺在每个Sprint里面完成某一些Story,或者在Kanban的模式中如下图所示,我们希望价值流动起来。想象一下,如果我们正在做story C或者D的时候,A和B正在测试,然后出现很多很多的bug。我们将会陷入到继续开发新特性和修改旧缺陷的摩擦里面,bug越多,这个摩擦越大。如果我们不能及时解决前面Story A或者B里面的bug,价值就流动不起来,很可能会阻碍后面的一系列行为,比如测试,发布等。但是如果及时地解决发现的bug,那么意味着我们正在开发新特性的过程会被不停地打断,这样会让我们的开发节奏混乱,并且效率低下。我们做C和D的时候,不会有“酷”的感受,只会感受到死亡行军的痛苦。甚至存在更糟糕的情况,等我们已经在开发G和H了,依然有来自A和B(发布阶段)和C,D(验收阶段)的bug,同时可能还需要处理G和H中的问题,这个摩擦带来的痛苦可想而知。只有质量内建,减少每一部分的缺陷,才能让价值的流动更平滑和顺利。价值的流动像水一样,我们不能让水逆流(频繁的回头修正以前的问题),也尽可能的不让水的流动停止下来。![](http://img.blog.itpub.net/blog/2020/07/07/7d11599886a0803e.jpeg?x-oss-process=style/bb)
2.3 无法预期的交付
迭代的开发让我们更早的发现问题,解决问题,从而在迭代结束的时候尽可能地按时交付客户价值增量。在进入迭代之前,我们会采用各种方式对将要开发的新特性进入工作量的预估,从而选择部分合适的user story作为迭代的承诺(Scrum),这样我们会对交付有一个预期,即使迭代结束不能完成所有的承诺,那么剩余的用户故事会delay多久,我们也能有一个相对清晰的预测。但是糟糕的质量将会摧毁我们的期待和预估。因为你无法预估在你接下来两周要走的路上有多少大坑,什么时候能够填满它们。坐在电脑前面,就像走在迷雾里面,无法看到尽头在哪儿,也不知道身处何方,只能埋头填坑,等着天光大亮才能看到路尽头的旗子。质量差的代码导致团队间的成员不容易互为备份,尤其是新人很难上手,甚至不得不重构。没有人愿意去动祖传的代码就是这个原因,因为代码的质量太差难以理解,难以扩展,架构不清晰,逻辑混乱,严重影响代码公有制的原则,一旦其他人涉及自己不够熟悉的部分,就会遇到上图所示的各种大坑,自然预期的交付时间就无从保证。软件开发过程往往是复杂和变化的,所以 批量的修复势必带来新的缺陷。
2.4 团队对质量失去信心
Bug也是累积的。你的bug越多,消除每一个bug也就越不容易。一定程度上是因为bug互相影响,表现出来的失败可能是很多错误共同的结果—这就导致很难找到每一个错误。这也是心理上的,当有很多bug的时候,人们自然就缺少激情去找到并解决这些bug—这是一种在《Pragmatic Programmer》一书中被称为 “破窗效应” 的现象。破窗效应(英语:Broken windows theory)是犯罪学的一个理论,该理论由詹姆士·威尔逊(James Q. Wilson)及乔治·凯林(George L. Kelling)提出。此理论认为环境中的不良现象如果被放任存在,会诱使人们仿效,甚至变本加厉。一幢有少许破窗的建筑为例,如果那些窗不被修理好,可能将会有破坏者破坏更多的窗户。![](http://img.blog.itpub.net/blog/2020/07/07/8b07f2c8476a8543.jpeg?x-oss-process=style/bb)
三、如何质量内建
3.1 需求分析
这是一个PO和团队持续对话的过程。 US和AC的澄清,是保障外部质量的一个最重要的手段或者说工具。 AC不是合同,US也不是用来交接的文档,他们的最重要的作用就是用来沟通。 需求的分析是我们质量内建的开始,输入的是垃圾,输出的一定是垃圾。 没有足够的需求分析和沟通,质量将无从谈起。在需求澄清时需要注意:以终为始,确保需求输入质量。 如我们一开始给出的敏捷三角形所示:
- 首先要讲解业务目标,也就是价值,换句话说就是要解决用户或业务什么问题。
- 其次操作及操作流程,为了实现上面目标,系统需要支持哪些用户操作?这些操作的流程是什么样的?
- 再次是业务规则,各个操作步骤对应的业务规则是什么样的?业务规则会转化成验收标准。
需求不是方案,而是用户的价值。 只有我们从价值开始,层层分解,从业务到实现层面充分沟通,才能保证后面交付的质量。
3.2 持续集成
持续集成并不是一个新鲜的概念,而是我们软件开发工作者生活中的一部分。简单来说持续集成是一种软件开发的实践,即团队开发成员经常集成他们的工作,通常每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽快地发现集成错误。许多团队发现这个过程可以大大减少集成的问题,让团队能够更快的开发内聚的软件。持续集成是我们质量左移的一个重要实践。 因为它可以带给我们非常及时的质量反馈,越频繁的集成,意味着越早期的发现代码中的问题。在持续集成的工程实践里面,我们需要自动化一切。因为频繁的集成意味着连续的和可重复性的工作。 自动化对于此类工作的可靠性远大于我们手工的操作。 作为工程师,代码就是我们的工具和武器,任何可以被自动化的地方,都应该用自动化的方式来实现,以减少人工失误带来的缺陷,并且可以释放我们的精力和时间,聚焦在更有价值和创造性的任务上面。在DevOps时代,如果想做到提交完代码后1小时上线发布,没有持续集成这个武器,质量将无从谈起。
![](http://img.blog.itpub.net/blog/2020/07/07/f40c5a38a9bfb042.jpeg?x-oss-process=style/bb)
3.3 测试先行
测试驱动开发(TDD): 测试驱动开发是是Extreme Programming (XP)--极限编程的一个重要组成部分。,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD虽是XP的核心实践,但不只适用于XP(Extreme Programming),同样可以适用于其他开发方法和过程。TDD比较重要的优点在于:
- 一是可以澄清需求,因为是测试先行,所以测试用例是根据需求来设计的,而不是根据代码来设计的,含糊不清的需求是没有办法进行测试用例的编写,必须予以提前澄清;
- 二是可以避免过度设计,只编写让测试用例通过的代码,多余的代码一行不写;
- 三是测试用例就是最好的代码注释,避免了没有注释/文档或者文档/注释过期的问题;
- 四是跟随代码提交的还有一个测试用例集,保障了将来重构和代码冲突时候的安全性。
这些优点将会是质量的有力保障。验收测试驱动开发(ATDD): 在代码层次,在编码之前写测试脚本就是上面所说的TDD,而在业务层次,在需求分析时就确定需求(如用户故事)的验收标准,就是这里所说的验收测试驱动开发(Acceptance Test Driven Development,ATDD)。TDD和ATDD的关系如下图所示。ATDD解决了TDD在实践中遭遇的一部分实际障碍:比如工期紧张然而单元测试需要的时间又比较多等。从需求的角度去准备验收标准和测试用例。同样可以保障从开发的开始就有较高的质量。
![](http://img.blog.itpub.net/blog/2020/07/07/8c655cca1c3215f9.jpeg?x-oss-process=style/bb)
![](http://img.blog.itpub.net/blog/2020/07/07/091507f13317fe0e.jpeg?x-oss-process=style/bb)
![](http://img.blog.itpub.net/blog/2020/07/07/d410bbe1a1b18b29.jpeg?x-oss-process=style/bb)
3.4 重构
在Martin Fowler的名著《重构》一书中,他把重构定义为:“在不改变代码外在行为的前提下对代码做出修改,以改进代码内部结构的过程。”可是为什么要修改正在正常运行的代码结构呢?我们应该都知道亨利福特有句话是“如果东西没有坏,就不要去修理它!”重构的目的是为了随时都在清洁你的代码。我们不想让脏乱积累。我们想通过最小的努力就能够对我们的系统进行扩展和修改。敏捷开发的原则之一就是每个迭代交付可用的用户功能,这样能够更快地获得用户的反馈,然后根据真实的数据和反馈不断改进。需求如此,代码亦如此。代码除了保持现状的功能运行,还必须可以足够健壮以应对随时带来的变化,如何让代码保持活力?重构是我们的不二选择。 重构也是消除我们文中提到的技术债的好办法。当然,也不是随随便便就可以重构的,重构之前,首先检查自己是否有一套可靠的测试机制。所有更好效果的工程实践,一定对于实践人员有着更高的要求!从来没有捷径可言。
3.5 Code review
对于Code review的必要性想来不需要再做重新说明,Code review的好处几乎不存在争议。 Code review既是质量的一道门禁,也是知识分享的一个很好途径。 那么如何保证review的质量?首先我们要 在团队的技术能力的不同阶段采用不同的review策略。 比如团队稳定,编码规范掌握的比较好,使用的语言也是熟悉的语言,那么可能review的重点就会放到业务逻辑方面;如果团队新成立,还在磨合,可能编码规范就需要多注意;如果是团队新换了一门编程语言,那么语法本身可能也会是review的重点。在公司业务发展的不同阶段也需要采用不同的review策略。 比如To B的业务在稳定推进,那么Code review可能需要更加仔细严格,保证质量和代码的可读性可维护性;但是如果是在互联网行业,业务发展初期,在快速试错阶段,那么Code review需要Technical Leader去平衡review力度和业务交付的要求。需要团队里有开放的文化和心态。 要团队清楚Code review并不是设置障碍,或者挑毛病,而是一个必不可少的质量保证过程,同时也是一个互相学习的过程。在这里需要对于编程规范有一个共识,避免因为编程习惯发生不必要的异议。控制每次需要Review的代码量。 如果一次提交大量的代码进行review,帮助做Review的人一个是时间上很难一次性拿出这么多的时间,另外一个是也很容易抓不到重点,同时提交代码的人在commit note中应该写清楚提交代码的目的:实现的是什么功能?做的是什么优化?修改的是什么bug?这样review的人才能有的放矢,清楚代码改动的上下文。最后要 让Code Review变成一种习惯,工作中的一部分, 那么就需要对Review者的时间有所保证,因为每个人在一个迭代中有自己承诺的任务,只有在预留了Review的时间的情况下,review才能作为一个日常任务被执行。可以把review作为DoD的一部分。
3.6 代码共有
代码共有是“共享与进步”精神的体现。它意味着每个人写的代码都是属于团队的,并且每个人都可以去修改任何代码。 集体所有权鼓励每个人为项目的所有部分贡献新的想法。任何开发人员都可以更改任意一行代码去增加新的功能、修复缺陷、改进设计或重构。没有人会成为变化的瓶颈。在没有集体代码所有制的情况下,会造成修改困难,重构困难,甚至会造成重复代码的出现,增加技术债务,破坏代码质量。而代码共有可以增强团队对于代码的ownership,全员都成为代码的负责人,他们互相监督,信息共享。 代码共有制的环境里,没有领导,没有权威,公平公开的环境让我们回到最本质的工程师文化里面,质量由此而生。
3.7 代码即文档
这是在诺基亚用过的一个工程实践,把代码作为主要的文档来源的理由是: 代码是唯一足够详细并且准确的执行该角色的代码。 尤其是在敏捷开发中,文档的地位越发不重要(虽然敏捷从没有说过不需要文档)。残缺的文档,过期的文档,错误的文档,反而会在开发维护中误导我们,造成不必要的缺陷和浪费。包括注释也是,如果一个程序员修改了代码,但是没有修改注释,就会造成比较大的麻烦,一旦出了问题我们就很难搞清楚是代码逻辑错了,还是注释过期了,尤其是在祖传代码里这种情况更常见。所以把代码当做文档来用,可以让我们的视野更清晰, 我们可以有一个唯一的质量标准:就是可工作的代码。为了保证代码即文档,程序员需要在保证代码的整洁和可读性上下功夫,如同重构部分所说,好的工程实践一定对应着更高的要求。参考文献:
- http://blog.cutter.com/2009/08/10/beyond-scope-schedule-and-cost-measuring-agile-performance/ 敏捷三角形 Jim Highsmith
- 敏捷12原则
- Scrum 指南