一、什么是质量内建

质量内建七步法 | IDCF FDCC认证学员作品_java



1.1 关于质量
在软件开发里面,质量是个永恒的话题,如下图所示,这是传统项目管理中的项目管理三角形:我们可以发现三角形的三条边成本,时间和范围所围绕着的是质量,也就是说质量被认为是必选的中心,不可以妥协的一个属性。质量内建七步法 | IDCF FDCC认证学员作品_java_02而在敏捷的项目管理中,这个三角形经过演进,结果如下图所示:质量内建七步法 | IDCF FDCC认证学员作品_java_03我们可以看到演进后的敏捷三角形里面,除了将范围变成了更聚焦于客户价值,还加强了质量的权重以提升终端用户的体验。所以对于质量,多重视都不为过。那么怎么更好地获得我们所关注的质量呢?尤其是在当前为了适应需求的变化,更灵活地应对不确定的市场的同时,如何保证质量变成了一个更突出的问题。要知道,对于敏捷开发而言,“快速”不是意味着“糙”,MVP是“简单”的但是并不意味着它是“简陋”的。对于质量的承诺,从未妥协和打折扣。我们认为质量不是后来添加的,质量不是测试出来的,质量是内建的!1.2 什么是质量内建质量内建作用在开发过程中,要求软件生命周期之间参与的各个角色都需要实时的对软件的质量负责。确保软件在交付到下一环节前已经有了基础的质量保证。其核心目的就是减少因为质量问题导致的返工,避免浪费大量人力成本。

1.3 精益敏捷中的质量内建

质量内建是精益核心原则之一,它有助于我们减少浪费,有助于避免与需求召回、返工及缺陷修复相关的延迟成本。如下图所示,我们可以知道:有大约85%的bug发生在coding阶段,但是解决他们的代价是最低的。也就是说,如果我们能够在coding阶段就避免掉这些bug,那么我们的产品里面就会减少85%的bug,从而避免了后面解决缺陷带来的指数增长的成本。也就是“问题发现的越早,修复的成本越低”。质量内建七步法 | IDCF FDCC认证学员作品_java_04质量内建也是敏捷的原则之一,敏捷宣言的十二条原则中其中一条就明确的提出要专注在质量上:“坚持不懈的追求技术卓越和良好设计,敏捷能力由此增强”。在Scrum 指南里面“Scrum事件”中关于“Sprint”的说明中明确指出:“在Sprint期间,不能降低质量的目标”。

1.4 SAFe里的内建质量

内建质量是SAFe的四个核心价值观之一。质量内建七步法 | IDCF FDCC认证学员作品_java_05内建质量能确保解决方案中的每一个要素和增量都符合质量标准,质量并不是“后来增加的”。内建质量是精益开发流的前提条件,如果没有质量,组织的运行可能伴随着大量的没有经过测试和验证的工作。这可能会导致过度的返工和更慢的交付速度。毫无疑问,内建质量对于大型系统而言是至关重要的(事实上内建质量对于任何一个系统都是至关重要的),质量是强制性的。



二、如果没有质量内建

质量内建七步法 | IDCF FDCC认证学员作品_java_06



如果没有质量内建,那么我们遇到的将会是

  • 不断增长的技术债,直到无力偿还。
  • 开发新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中的问题,这个摩擦带来的痛苦可想而知。只有质量内建,减少每一部分的缺陷,才能让价值的流动更平滑和顺利。价值的流动像水一样,我们不能让水逆流(频繁的回头修正以前的问题),也尽可能的不让水的流动停止下来。质量内建七步法 | IDCF FDCC认证学员作品_java_07

2.3 无法预期的交付

迭代的开发让我们更早的发现问题,解决问题,从而在迭代结束的时候尽可能地按时交付客户价值增量。在进入迭代之前,我们会采用各种方式对将要开发的新特性进入工作量的预估,从而选择部分合适的user story作为迭代的承诺(Scrum),这样我们会对交付有一个预期,即使迭代结束不能完成所有的承诺,那么剩余的用户故事会delay多久,我们也能有一个相对清晰的预测。但是糟糕的质量将会摧毁我们的期待和预估。因为你无法预估在你接下来两周要走的路上有多少大坑,什么时候能够填满它们。坐在电脑前面,就像走在迷雾里面,无法看到尽头在哪儿,也不知道身处何方,只能埋头填坑,等着天光大亮才能看到路尽头的旗子。质量内建七步法 | IDCF FDCC认证学员作品_java_08质量差的代码导致团队间的成员不容易互为备份,尤其是新人很难上手,甚至不得不重构。没有人愿意去动祖传的代码就是这个原因,因为代码的质量太差难以理解,难以扩展,架构不清晰,逻辑混乱,严重影响代码公有制的原则,一旦其他人涉及自己不够熟悉的部分,就会遇到上图所示的各种大坑,自然预期的交付时间就无从保证。软件开发过程往往是复杂和变化的,所以批量的修复势必带来新的缺陷。

2.4 团队对质量失去信心

Bug也是累积的。你的bug越多,消除每一个bug也就越不容易。一定程度上是因为bug互相影响,表现出来的失败可能是很多错误共同的结果—这就导致很难找到每一个错误。这也是心理上的,当有很多bug的时候,人们自然就缺少激情去找到并解决这些bug—这是一种在《Pragmatic Programmer》一书中被称为“破窗效应”的现象。破窗效应(英语:Broken windows theory)是犯罪学的一个理论,该理论由詹姆士·威尔逊(James Q. Wilson)及乔治·凯林(George L. Kelling)提出。此理论认为环境中的不良现象如果被放任存在,会诱使人们仿效,甚至变本加厉。一幢有少许破窗的建筑为例,如果那些窗不被修理好,可能将会有破坏者破坏更多的窗户。质量内建七步法 | IDCF FDCC认证学员作品_java_09破窗理论能够很好地解释人们违反代码整洁之道,对质量问题视若罔闻的背后原因:是因为原来就是垃圾,别人做了错误的行为没有受到惩罚或者约束,那么我做同样的事情和行为也是正常的,大家都一样。但是所谓的没有关系的错误或者瑕疵被放过去,累积起来,等雪崩的时候,没有一片雪花是无辜的。最后整个团队为质量买单。怎么避免破窗效应?让我们的质量能够保持在一个健康的状态呢?应对方式就是童子军军规:“当你离开一个露宿营地的时候,一定要让它比你来的时候更整洁干净一点。”我们的每次提交都应该让代码比前一个版本更干净一点,哪怕是减少一部分重复代码,修正一个格式问题。这样也能让我们远离代码的坏味道,保持我们代码和架构的整洁。 



三、如何质量内建

质量内建七步法 | IDCF FDCC认证学员作品_java_10



3.1 需求分析

这是一个PO和团队持续对话的过程。US和AC的澄清,是保障外部质量的一个最重要的手段或者说工具。AC不是合同,US也不是用来交接的文档,他们的最重要的作用就是用来沟通。需求的分析是我们质量内建的开始,输入的是垃圾,输出的一定是垃圾。没有足够的需求分析和沟通,质量将无从谈起。在需求澄清时需要注意:以终为始,确保需求输入质量。如我们一开始给出的敏捷三角形所示:

  • 首先要讲解业务目标,也就是价值,换句话说就是要解决用户或业务什么问题。
  • 其次操作及操作流程,为了实现上面目标,系统需要支持哪些用户操作?这些操作的流程是什么样的?
  • 再次是业务规则,各个操作步骤对应的业务规则是什么样的?业务规则会转化成验收标准。

需求不是方案,而是用户的价值。只有我们从价值开始,层层分解,从业务到实现层面充分沟通,才能保证后面交付的质量。

3.2 持续集成

持续集成并不是一个新鲜的概念,而是我们软件开发工作者生活中的一部分。简单来说持续集成是一种软件开发的实践,即团队开发成员经常集成他们的工作,通常每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽快地发现集成错误。许多团队发现这个过程可以大大减少集成的问题,让团队能够更快的开发内聚的软件。持续集成是我们质量左移的一个重要实践。因为它可以带给我们非常及时的质量反馈,越频繁的集成,意味着越早期的发现代码中的问题。在持续集成的工程实践里面,我们需要自动化一切。因为频繁的集成意味着连续的和可重复性的工作。自动化对于此类工作的可靠性远大于我们手工的操作。作为工程师,代码就是我们的工具和武器,任何可以被自动化的地方,都应该用自动化的方式来实现,以减少人工失误带来的缺陷,并且可以释放我们的精力和时间,聚焦在更有价值和创造性的任务上面。在DevOps时代,如果想做到提交完代码后1小时上线发布,没有持续集成这个武器,质量将无从谈起。质量内建七步法 | IDCF FDCC认证学员作品_java_11

3.3 测试先行

测试驱动开发(TDD):测试驱动开发是是Extreme Programming (XP)--极限编程的一个重要组成部分。,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD虽是XP的核心实践,但不只适用于XP(Extreme Programming),同样可以适用于其他开发方法和过程。TDD比较重要的优点在于:

  • 一是可以澄清需求,因为是测试先行,所以测试用例是根据需求来设计的,而不是根据代码来设计的,含糊不清的需求是没有办法进行测试用例的编写,必须予以提前澄清;
  • 二是可以避免过度设计,只编写让测试用例通过的代码,多余的代码一行不写;
  • 三是测试用例就是最好的代码注释,避免了没有注释/文档或者文档/注释过期的问题;
  • 四是跟随代码提交的还有一个测试用例集,保障了将来重构和代码冲突时候的安全性。

这些优点将会是质量的有力保障。验收测试驱动开发(ATDD):在代码层次,在编码之前写测试脚本就是上面所说的TDD,而在业务层次,在需求分析时就确定需求(如用户故事)的验收标准,就是这里所说的验收测试驱动开发(Acceptance Test Driven Development,ATDD)。TDD和ATDD的关系如下图所示。ATDD解决了TDD在实践中遭遇的一部分实际障碍:比如工期紧张然而单元测试需要的时间又比较多等。从需求的角度去准备验收标准和测试用例。同样可以保障从开发的开始就有较高的质量。质量内建七步法 | IDCF FDCC认证学员作品_java_12行为驱动开发(BDD):BDD可以看成ATDD的延伸,只是BDD更强调用户的视角、用户的行为,为ATDD注入了“Given,When,Then”这样特定的需求描述语言。和敏捷的user story极为吻合。TDD在写测试用例时,常常会提出“我们应该先测什么”,然后针对测试的条件来填充代码,而BDD则试图换一种方式去思考问题,即问自己“预期的行为是什么?”,可能会写出结构更好的代码。说到底,BDD更关注客户的需求,通过了解客户的不同行为,对客户的需求有更深刻的理解,从而借助对需求逐渐深入的理解来驱动软件开发。下图就是一个BDD的典型case:质量内建七步法 | IDCF FDCC认证学员作品_java_13而BDD和TDD的关系则可以通过下图清晰的看出来区别所在:质量内建七步法 | IDCF FDCC认证学员作品_java_14

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 代码即文档

这是在诺基亚用过的一个工程实践,把代码作为主要的文档来源的理由是:代码是唯一足够详细并且准确的执行该角色的代码。尤其是在敏捷开发中,文档的地位越发不重要(虽然敏捷从没有说过不需要文档)。残缺的文档,过期的文档,错误的文档,反而会在开发维护中误导我们,造成不必要的缺陷和浪费。包括注释也是,如果一个程序员修改了代码,但是没有修改注释,就会造成比较大的麻烦,一旦出了问题我们就很难搞清楚是代码逻辑错了,还是注释过期了,尤其是在祖传代码里这种情况更常见。所以把代码当做文档来用,可以让我们的视野更清晰,我们可以有一个唯一的质量标准:就是可工作的代码。为了保证代码即文档,程序员需要在保证代码的整洁和可读性上下功夫,如同重构部分所说,好的工程实践一定对应着更高的要求。参考文献:

  • https://www.scaledagileframework.com/safe-core-values/
  • http://blog.cutter.com/2009/08/10/beyond-scope-schedule-and-cost-measuring-agile-performance/ 敏捷三角形 Jim Highsmith
  • http://agilemanifesto.org/principles.html 敏捷12原则
  • https://www.scrumguides.org/docs/scrumguide/v2017/2017-Scrum-Guide-Chinese-Simplified.pdf Scrum 指南

质量内建七步法 | IDCF FDCC认证学员作品_java_15


资深Scrum Master,软件研发经理敏捷项目经理践行精益敏捷,保持终身学习怕什么真理无穷,进一寸有进一寸的欢喜