前面两篇分别介绍了我们是如何通过建立可视化的交付流水线来使得整体交付过程更加紧密顺畅,以及如何通过各类自动化手段来提升整体的交付效率;这就好比汽车生产流水线的引入和机械化升级分别带来了前两轮汽车生产效率的革命性提升。而汽车生产效率的第三轮革命性提升可以说是由丰田公司提出的“精益制造”所引发的,其强调的理念是实时监控生产品质,以便及早发现问题加以改进。管理学之父彼得-德鲁克也说过——“无法度量,就无法改进”,度量能帮助我们更深刻的认知研发效能,设定改进方向,并衡量改进后的效果。所以,想要持续的提升效能,最首要的任务就是建立度量效能的能力,以便更好的识别交付过程中的质效瓶颈。本文主要介绍下我们在交付质量及交付效率两方面的度量实践。
二、指标度量体系搭建 2.1、质量度量代码质量在研发过程中是一个很重要的因素,更好的代码质量能产生更少的 BUG,也能降低代码的维护成本使开发人员更不容易犯错,从而使产品的质量得到提升。那么,如何定义代码质量,如何测量并可视化的展现就成了我们探索的方向。我们的代码质量监控体系从无到有,从基础检测到系统性评测大致经历了如下 3 个阶段。
1)第一阶段:质量标准确定这一阶段我们主要目标是快速建立代码规范及对应的自动化检测手段,所以我们更多的是考虑高性价比的方案,比如引入第三方检测工具。SonarQube 是一款目前比较流行的工具,采用 B/S 架构,通过插件的形式可以支持 Java、C、C++、JavaScript 等二十多种编程语言,同时也可以集成不同的检测工具和规则,比如 pmd-cpd、findbugs、P3C 规范等,能较方便的实现基本的代码质量检测与管理。所以最终我们将 SonarQube 作为了主要的检测工具,通过与各业务线研发负责人梳理并调整对应的规则配置,形成了各业务线基础的检查规则。
而 Sonar 作为一款大而全的工具,对于部分特定的语言来说,规则的完备性和全面性肯定没有专门针对该语言的单一工具更好。对于特定语言的规则我们后续又逐渐引入了一些针对性更强的检测工具,比如 360的火线,该工具在安全漏洞、内存泄露风险方面的检查相比 sonar 会更加完善。
另外,对于业务特性比较强的一些规则,市面上的这些工具就无法很好的支持了,针对这种规则我们主要是基于正则和语法树解析的方式来实现自定义的检查规则。最终形成的检测工具集如下:
2)第二阶段:度量指标设计
上述规范落地执行一段时间之后,我们部门内的严重等级以上的规范性问题基本做到了清零,但是还是会存在各种各样的痛点,比如:简单的规范性扫描和 code review 只能解决代码风格及少部分业务逻辑问题,很难发现重复率、复杂度、用例失败等方面的问题;某个项目针对部分模块进行了重构、增加了许多 case,整体代码质量是变好还是变坏了?这个变化如何评估,如何量化?
针对上述问题,我们希望进一步优化代码质量评测体系,达成以下 2 点要求:
- 多维度、全方位评测:提供多维度的代码检测方式和评测手段,而不仅仅已代码违规数来作为唯一衡量标准
- 可量化、可视化:能数值化、可视化的展现代码质量,能方便直观的看到代码所存在的问题及整体质量变化趋势
《Sonar code quality testing essential》一书中从编码规范、潜在 BUG、文档和注释、重复代码、复杂度、测试覆盖率、设计与架构 7 个维度定义了代码的内在质量。为了更精细合理的度量代码质量,我们选取了3个与代码内在质量强相关的维度纳入我们的评测体系:
- 代码规范性得分:代码规范性是指否遵循了编码规范及最佳实践;我们主要通过Sonar、ESLint 等第三方工具及我们自研的工具对代码进行扫描,根据最终发现的问题数量结合权重系数,综合计算出一个代码规范性得分
这里额外提一下权重系数,它跟问题的严重等级,项目的规模大小都有关系;问题的严重等级好理解,不同等级的问题扣分不同;项目规模大小可以理解为问题密度:因为我们认为一个10万行代码的项目出现10个严重问题,和一个1千行代码的项目出现10个严重问题,这两者的质量情况是不一样的
- 代码重复率得分:重复率顾名思义就是重复代码的比例了。重复率较高的代码,一来增加代码体积、二来可能会出现代码漏改导致问题。我们通过统计项目代码的整体重复比例,然后按照一定的标准计算得到代码重复率得分
- 代码复杂度:较低复杂度的代码能提高可读性、降低维护成本,避免一些不可控的问题出现。我们通过统计项目的整体复杂度,结合代码总行数,得到千行代码复杂度,然后按照一定的标准计算得到代码复杂度得分
最终,我们将以上3个维度的得分综合加权,得到一个100分制的分数,我们内部称之为“代码健康度”。然后结合代码健康度这个指标,在日常工作中去评测我们的代码质量,并推动相应的质量改进。
3)第三阶段:优化改进推动都说规范易定,落地难。尤其是在产品快速迭代的情况下,仅仅是为了满足正常的业务交付大家就已经开足马力了,提升代码质量这种看不到显著短期收益的事情大家往往容易忽略。那我们是如何真正落地这个代码健康度指标,并推动大家在研发过程中去及时改进的呢?我们主要做了下面两件事情
1、提高认可度我们做的第一件事情,就是不断优化和完善健康度算法,使得该数据更加贴近大家的主观认知。也就是说当大家都认为一段代码比较烂的时候,其健康度评分也应该是比较低的,反之亦然;只有这样才能一定程度上的节省人工code review的成本,让大家愿意参考和使用这个指标。在这方面我们主要做了以下3点:
- 权重优化:比如严重问题应该扣多少分?次要问题又应该扣多少分;不同的项目规模对这个权重系数的影响应该是多大?代码的重复率和复杂度多少算是优秀?等等...所有的这些权重系数,我们都跟研发团队一起,结合具体的业务场景进行了多轮的调整和优化,并非是一蹴而就的
- 算法调整:比如复杂度我们最初使用的是圈复杂度,它用来描述一段代码的“可测性”很好,但是却很难得出代码的“可维护性”方面的结果,而且对于一些特定的场景的评判会不太客观,所以我们最终替换为了更准确的认知复杂度(详情大家可以参考这篇文章:浅析代码圈复杂度及认知复杂度)
- 查漏补缺:这一点主要是指我们会针对日常code review过程中所发现的问题,不断地补充和完善我们的代码检测规则池;同时也会结合实际的业务情况对一些配置类、thrift自动生成的代码进行过滤,以使得评测范围更加的精准
在大家逐渐认可了这个指标之后,我们加下来要做的就是如何更有效的推动大家进行改进了,我们主要采取了两类实践。
① 建立质量大盘dashboard:针对存量代码或者说技术债务,我们通过定期扫描来监控部门内所有项目的整体质量情况。另外,我们也通过打通内部的服务治理系统并结合各维度的数据统计进行了代码质量的可视化呈现,让大家能够很直观的看到部门、模块、小组、人员等维度的代码质量情况,方便大家进行横向比较,让代码质量好的同事和小组起到一个带头示范的效应;同时平台上也支持大家进行阶段性的健康度目标设定(类似OKR一样),并通过将现状与目标的差异显性化的展示出来,让大家更有目标感和使命感。
② 增加流程中的质量门禁:在解决历史债务的同时,我们也希望能在研发流程中对增量代码进行控制,以避免一边在解决历史问题,一边又不断新增问题的情况出现。所以我们最终结合gitlab api获取每个需求分支对应的差异代码,计算出增量代码的健康度,并在研发流程中设置相应的检查门禁来进行管控(增量代码健康度若<80则会阻断流程)
最终,我们与研发团队共同配合,基于这套可量化、可视化的代码质量评测体系,在严格管控增量代码质量的同时,又不断推动解决存量的技术债务;在近几年实现了部门核心项目代码健康度的逐年上升,对最终产品质量的提升起到了正向促进作用。
2.2、效率度量
前文我们提到了,只要有人工参与的环节就或多或少会存在效率方面的损耗。为了更好的分析研发过程中存在的效率瓶颈,我们结合自研的交付流水线对研发流程中各个节点进行了埋点统计,实现了对各个阶段、各个环节的效率数据统计和监控;同时也搭建了相应的可视化指标平台,方便及时发现和分析效能问题。
然而,不同公司的业务形态、组织架构不同,对应的研发流程也不尽相同,很难说有一套银弹能够解决所有的效能问题。下面我就举几个案例,来说明下我们在效率优化方面所做的改进,希望能起到抛砖引玉,引发大家思考的这么一个作用。
1)流程改进案例不同于普通的业务需求,hotfix类的需求一般都是针对线上问题,需要紧急修复上线的,如果还走原来那一套研发-测试-集成-灰度-发布的流程,就显得有点繁琐了,满足不了快速上线的要求。针对这种场景,我们这边的玩法是建立绿色通道,就是尽可能的精简整个交付流程,仅仅保留必要的自动化验证环节(代码扫描、单元测试)以及最终上线前的负责人审核确认环节。研发团队可以根据实际情况来选择是走普通通道还是绿色通道,以此来做质量和效率方面的取舍和平衡。
除了上面提到的这种大刀阔斧的流程精简之外,我们也是做了不少比较细的优化改进的,比如我们原先的发版审批流程如下图所示:
普通需求的审批流程这样设定是没什么问题的,但是对于一些技术需求,开发负责人和产品负责人可能是同一个人,原先这个审批顺序就相当于把一个人的审批动作强行给分隔开了,会造成无谓的过程等待。最终我们通过调整审批顺序优化了这个问题。虽然这个改动点比较小,对于单次审批过程来说,其提升的效率是有限的。但是基数大了之后,对于团队整体的效能提升还是很可观的。
2)技术改进案例除了流程方面的优化,也有一些改进是通过技术手段完成的。随着产品的迭代、功能不断增多,对应的自动化用例数量也随之不断增加,我们发现用例执行的耗时越来越长逐渐到了不可忍受的地步。为了解决这个问题,我们主要做了以下几个方面的优化,最终实现了用例执行耗时的大幅下降:
- 首先,是采用并发执行机制,用硬件换时间。
- 其次,是重跑机制的优化。当检测到没有代码变更时,对于重新构建的场景我们会只执行上一次出错的用例而非全量用例,从而节省不必要的耗时。
- 最后,就是整体执行策略的调整。在研发流程中我们只会执行服务相关的核心用例,而将全量用例的执行留到夜间闲时去执行,以追求质量和效率上的平衡。
借鉴业界的一些优秀实践经验,在效能度量和改进的落地实践过程中我们也逐渐摸索到了一些套路,我觉得有以下几点值得跟大家分享和探讨一下:
1、指标要有目标引导性指标的定义一定是需要有目标引导性的,不能是虚荣性的指标。举个例子:接入代码健康度检测的项目数就是虚荣性指标;而特定项目或者整体大盘的代码健康度变化趋势就是一个目标引导性的指标。
2、指标需要有全局视角很多时候我们往往关注于优化某个具体的点,而忽略了全局。就好比前面我们之前提到的组织之间的谷仓困局。所以指标的度量不光要考虑单个节点(比如代码扫描时长、单元用例执行时长),也要考虑流程之间的流转耗时、环节之间的等待耗时等。
3、指标并非越多越好不建议在没有任何改进目标的前提下去进行大规模的度量,因为度量本身也是需要成本的。就好比我们去医院看病,医生一定是先询问症状,而不是一上来就让我们做一套全身检查。比较理想的做法是先对整个研发过程进行深度的分析,发现对应的改进点之后,再针对性的设计度量策略并采取相应的优化措施,最后再通过度量数据去验证改进的效果。从而形成发现问题-分析问题-改进问题的这样一个闭环。
4、指标计算需要有可靠输入度量指标的计算因子要有可靠的信息输入,不能依赖人工填写。比如开发完成时间,就不能靠开发人员去手填,而是应该由提测前的最后一步自动化构建去自动生成。
5、不建议直接作为KPI任何指标都无可避免的存在一定的片面性,如果强行把某些指标作为KPI往往会导致执行落地过程中变形。如果觉得这个指标比较重要,更推荐的做法是采取一定的措施,让大家更有目标感和使命感(类似上面提到的质量看板),以此来推动改进。
四、结语以上就是我们在指标度量体系搭建方面所进行的一些探索和实践,至此,这个持续交付系列的文章也算是接近尾声了。写这些内容主要目的是为了对自己的工作能有个阶段性的总结,想通过输出来倒逼自己输入,毕竟心里明白一件事情和把一件事情讲明白还是有点差别的。当然了,如果大家能觉得这些内容其中的某一个小点有一定的参考或借鉴的价值,那也算是我的额外收获了。
其实目前我们这套持续交付体系还存在着不少优化空间,随着未来业务的发展,仍然不断会有新的困难和挑战,在效能改进方面我们依然需要不断的探索。争取后面再抽空整理一篇整体的回顾和总结,来阐述下我们系统的整体架构、过程中碰到的一些典型问题和决策、以及对于未来优化改进的一些思考。
相关阅读:
持续交付探索与实践(一):交付流水线的设计
持续交付探索与实践(二):自动化工具链建设