一致性,是研发效能提升的必经之路!

| 2021-07-20

「一致性会为组织带来逆熵,使其在规模化的过程中更加有序,从而带来效能提升(至少可以保持不降)。

为系统化组织注入一致性,要从三个方面入手:流程机制、工具平台、人员能力。」

本文来自在腾讯 CI-Day 的主题分享。由于一些原因,内容有删减,请读者见谅。

尽管我们在现代化工业社会,但「软件开发」这个事儿真的很现代化么?

并不是,而是纯粹的手工业。也就是:

一大群掌握特定技能的手工业(知识工作)者面对正在运行着的生产机器(运行中的软件),每天各自生产着不同的零件,既要不断把零件安到生产机器上,还要不影响这台机器持续生产的过程。

内容概要

研效所要解决的真正问题是因规模扩大所带来了复杂性,从而导致的生产效率下降。

为了让规模化的同时保持单位生产效率不降低,甚至提升效率,要从流程机制、工具平台、人员能力入手。

  • 流程机制的建设要结合市场竞争和内部协作团队的能力,但应具有一致性;

  • 工具平台应该能支持流程机制的一致性要求;

  • 工程师除了要持续改进流程机制与工具之外,还要能够坚持工具平台尚无法完全提供的一致性,例如代码编写规范,代码设计范式等等。

在大团队中,只有保持一致性,才能让沟通成本最低,知识的传递成本最低,单位生产效率才能提升。

一致性的管理方法在提升研效方面至关重要,基于这一认知,乔梁引入了“道、法、器、术”这一步骤来达成一致性。

道,是指工作中所秉承的方法论。只有在相同方法论的指导下,才能达成工作中的一致性,否则,大家会有不同的理解。

在方法论一致的情况下,必须能够将方法论具象化为工作中的具体要求与规范,这就是“法”。

在此基础之上,才能有力支撑“器(工具平台)”和“术(具体实践)”之间的一致性。如果没有“道”与“法”,而仅仅关注“器”与术,那么,在多团队协作过程中,很难达成一致性。

“ 持续交付2.0 双环模型”就是一种软件工程方法论,它包含是重要的指导思想和一系列的工作基本原则。

“ Product Development Competence( EPC)”就是由它派生的要求与规范。其中包含一个结果维度,十二个过程维度。每个维度都有对应的规范,讲解了“是什么,做什么,怎么做”。(参见《持续交付2.0 (精装版)》第十七章 胜任力模型与组织健康度)。

工具建设和具体实践应该理解和遵从这些“规范”。

简而言之:一旦具有一致性,那么,即使规模变大,系统也会简单而可靠。

下方是完整速记

软件开发就是手工业者的大规模协作生产

乔梁:大家上午好,这是我第二次参加腾讯 CIday。今年现场的人数比去年多不少,去年可能是因为疫情的原因,现场的人数很少。

我今天给大家分享的题目是“一致性是研发效能的必经之路”。请注意,是“必经之路”,不是保证成功之路。因为任何一件事的成功,都是很多因素作用的结果。

我算是软件行业的一名老兵,工作 20 多年,从写代码,做项目经理,到开发第一款软件产品,直到现在指导很多组织做改进。这个过程中,我翻译了一本书,并写了一本书。翻译的是《持续交付》,该书获得了 2011 年 Jolt 杰出贡献奖,大家耳熟能详的“部署流水线”,它的定义就出自这本书。写的是《持续交付 2.0 》,2019 年出版。是我在过去十年的工作经历中更多以业务视角来看待持续交付后的工作方法总结,它是持续交付的升级版。

可能很多人没有看过这两本关于“软件工程”的大部头书籍,但是我相信在座的各位,很多人知道 EPC ,也有很多人对吐槽 EPC,而我就是 EPC 的创造者。估计很少有人真正知道EPC到底是什么的缩写,它是“ Engineering Productivity Compentence ”,即“工程效能胜任力”。

胜任力与能力的区别在于:胜任力必须要在日常工作中展现出来的,而“能力”却不一定必须要在日常工作中发挥作用。

现在,让我们回归到我们今年的主题。先讨论一下,为什么“研发效能提升”现在又这么火了?

其实,在过去的二十年,它在国内已经火过至少三次了,只是使用了不同的“流行词”。它们是“敏捷”、“精益”,和现在的“ DevOps ”。事实上,它们的核心其实相差无几。原因在于:软件本身是一种特殊的产品。

其特殊性在于,“设计活动”与“制造活动”无法分开。软件规模虽然在不断扩大,但并非重复,每一个工程师写出的代码都是不一样的,这些代码要被不断组装在一起,然后让这个软件系统能够持续工作。

而且,软件每天都在变化,工程师每天都写不同的代码,相当于制造互不相同的机器零件,每天都要把这些新零件安装到运行当中的软件系统上,并且要保持工作状态。所以,在这个行业里,我们一直都是手工业者,和原来打铁匠的工作性质类似。

回到研效的问题,为什么每隔一段时间就会火一下?是因为每隔一段时间,业务增长都会遇到一些瓶颈。

1 研发效能为什么又火了?

研发效能为什么又火了

如图所示,一个公司在小的时候,人数不多,软件相对也不复杂,只要找到合适的场景,业务就会快速增长。随着业务场景的增多,需要的人员也就越多,协作也就越多,单位生产效率也会随之下降。由于业务增长够快,所以此时的效率下降并不容易被感受到。或早或晚,业务增长都会遇到某个瓶颈,此时,组织就会感受到效率问题。而且,随着业务压力的增大,大家对研发效率的预期越来越高。

然而,由于我们是手工业生产,业务和协作场景越多,单位效率就越低。这一点从来都没有改变过。而研发效能提升的压力就来自于对业务侧对效率的预期及真实效率之间的反差。

我们能否极大地改善或提升研发效率呢?在短期内,这是一个不切实际的幻想。

2 熵增定律与复杂性

出版于 1987 年的《人件》一书指出了这一点。技术可能是主要问题之一,但绝对不是唯一的问题,更大的问题在于:很多人协作,效率就会降低。

根据熵增定律,一个复杂系统在没有外力的作用下,它会变得越来越混乱,直到无序。而这个软件系统和生产这个软件的组织,就是一个复杂系统。

软件架构从最早的单体应用到 SOA 化,再到现在的微服务化,其整体复杂度越来越高。现在,我们需要开发更多的软件来管理现在这种庞大的基础设施,也就是需要使用另一种复杂系统来管理当前软件的复杂度。

那么,复杂性有多少种类型呢?一种是被解决问题的复杂性,即问题领域自身的复杂性。想要解决问题,就要有相应的解决方案,所以,第二种是解决方案自身的复杂性。而解决方案领域复杂性又可以细分成两种,一是系统化复杂性,也就是解决方案的本身不可降低的复杂性,二是随机复杂性(或叫偶然复杂性)。由于很多偶然情况所引入的复杂性。在研效领域,非常重要的一点就是要克服这种随机复杂性。

这种随机复杂性是从哪来的?我们举一个例子。

3 引入随机复杂性,原因有多少?

假如有三个团队,一个团队(A)是生产SDK的,但 A 并不知道有多少个团队使用了这个 SDK 。现在,已知有两个团队( B 和 C )各自使用这个 SDK 开发了自己的软件。当 B 软件发现一个 Bug,而这个Bug 是由 SDK 的一个缺陷导致的。最好的办法是由 A 团队在 SDK 上修复这个Bug。但可能因为种种原因,A 无法立即在 SDK 上修改。所以,使用方就只能各自想办法,在自己的软件上打个补丁,修复这个问题。然而,这个补丁很可能为各自团队又带来了各自不同的问题。假如 B 或 C 还有下游团队的话,就会出现传导,变成了补丁上打补丁……,最终你的产品就不像你规划的那样了,这就是随机复杂性。

其关键在于,对于同一个问题的修复出现了两种不同的解决方案,每种解决方案都可能带来不同的引申问题,而这些引申问题的解决方案也都是不一致的,无法被统一管理。这样,问题的数量在不断扩大,无法收敛,只因为 SDK 的 Bug 没有被修复掉。这就是随机复杂性的一个例子,发生在解决方案领域的随机复杂性。

再讲一个随机复杂性的例子,是多人协作中的随机复杂性。当某个开发人员开发完了一个功能以后,会交给测试人员进行测试。结果,这个特性在测试环境中出了问题。开发人员说:“在我这里没有问题啊!”测试人员说:“我这里可以复现呀”。

找了半天,最后发现,这个特性需要修改一项配置信息,开发人员忘记告诉测试人员了。这就是协作中的随机复杂性,它大大消耗了资源。因为需要反复沟通确认。还有什么样的偶然复杂性?

我们写代码都是在一定的约束条件下进行软件开发的。你不会有无限时间,无限资源写最完美的代码。所以,我们一定是带着技术债前行的,只有多少之分。这些技术债都可能产生偶然复杂性。

4 研发效能提升,从哪里开始?

我们举了这么多的例子,说明了哪些问题呢?

第一,规模化提高了复杂性,复杂性中既有本质复杂性,也有随机复杂性。首先要承认,研效提升无法保证业务一定能够成功,不承认这一点的话,我们就不用继续讨论了。

第二,业务发展和市场竞争对研发效率的要求不断提高,这一点我们也改变不了。

第三,研发效率这条灰色的线,我们也改变不了它向下的趋势。

这三点我们目前都无法改变,那么研效最终作用在哪里呢?

我们只能想办法做到:随着规模的扩大,研效不要降低得那么快,这是对研效工作最基本的要求。研效要解决的真正的问题来自于你的规模扩大,即:如何让你的效率保持和你原来的效率基本差不多。当然,如果某一天发明一种“核武器”,改变了软件的生产方式,可以这个结论就不成立了。

小团队刚启动的业务时,效率重不重要?并不重要,有效性才是重要的,所有成功的前提是有效,在“效率”和“有效性”两个之间,有效性更重要。德鲁克说:有效性就意味着效能,所谓的效能就是你的组织效能。

谁能够评价你的组织效能呢?组织内部是无法评估你的组织效能的,只有企业外部能够评价你的组织效能。但是,效率是组织内部的事情,是可以改善的。所以,我们今天讨论的根本不是“效能”,而是“效率”。

复杂性带来了效率下降。所以,提升效率的方法就是有效地降低复杂性,而降低随机复杂性的方法是提高一致性。对于一个系统来说,一致性本身会带来逆熵,减缓系统的熵增,让系统相对有序,从而提升效率,或者至少保证效率不降低得很快。

4. 一致性与工程师文化

如何为系统注入一致性呢?有三个方面,它们分别是:流程机制、工具平台、人员能力。

大家都在说工程师文化,文化就深植于这三个方面,我们必须通过这三方面的建设,才能打造我们想要的工程师文化。文化是这三方面相互作用的结果,它既不是过程,也不是方法。

所以,当我们讨论工程师文化的时候,应该讨论的是流程机制、工具平台和人员能力。因为它们反映了你的工程师文化。

开发软件,其基本流程是一致的。曾经有人提出了一个问题:哪一种分支模型更适用?当我们讨论一个流程、一个制度、一个协作模式的时候,你就必须考虑它的市场场景、它的组织内部协作人员的场景,再加上自己团队的能力。我在这个问题的回复中写了:团队的能力决定了你能够驾驭多少种流程。如果你的能力足够高,你就可以驾驭各种各样的分支模式。但如果你的分支模式超越了你的人员能力,那么你就要思考以选择哪一个为主,如何折中了。

昨天晚上召开了一次研效委员会工作组例会,讨论研发数据规范,其中就有一个议题是分支模式规范。最终结论是,不同团队使用的分支模式太多了,无法规范。也就说,目前根本不具有一致性。

然而,要支持这么多种分支模式,做支撑平台的团队效率一定是很低的。2018 年时,我们面对的就是这种情况。如果要支持一种模式,我需要花的成本是 10 的话,支持十种模式,需要花费的成本就会比 100 还要多。我想,toB 业务也面临着类似的问题。定制化软件的成本是巨高的,尽管看起来都解决的是同一领域的问题。为什么会有定制化?当然是由于不一致性。

讲到一致性,我们可以用“编写程序”来举例。当你写下第一个程序,甚至是在大学一年级写 Hello World 时,你可能就已经开始程序员之路了。作为一名程序员,当我们写某个算法时,可能更加关注它的功能实现、关注性能。这是程序员凭直觉就应该干的事。特性上线能用,是程序员的主要目标。

然而,作为一名软件工程师,你必须还要考虑一个非常重要的因素,那就是“时间”。工程师需要保证软件的可持续性,必须考虑时间维度所产生的影响。这种时间维度的影响可能包括:

  • 几个月后,你自己再次看到这段代码时,是否能够马上理解?
  • 你离开团队以后,别人接手时是否容易看得懂?
  • 在新增需求时,这段代码是否容易改,是否容易测试?
  • 在其依赖发生变化时,这段代码是否还能正确地工作?

如果没有考虑这些,代码的可维护性会很差。可维护性差的影响并不容易马上被人感知,可能每天变差一点点。

那么,怎么解决这个问题?就是要有一致性要求。这种一致性要求可能体现在多个方面,比如,代码规范就是一种“一致性”要求。代码应该写成什么样,不应该依赖某个人的喜好而转移,因为你要考虑的是很多人,而不是一小撮人。

对于代码规范,这里也有一个人问:

「有没有一种因人而异的实施方法,毕竟每个程序的流派和审美观都不相同,如何把握这个自由度?」

我刚才已经给出了答案。这个要看你站在哪一个角度回答这个问题。如果你自己编程,或者是与其他人毫无关系,可以有任意自由度,因为与他们无关。

但是,我们是在同一个组织中,很多人编写代码,你可能会换岗位,也有可能会离职。别人接手你的代码时,会怎么样?

从 2020 年底,我们就开始筹备 Readability 培训。为什么会有这个培训?其实也是对一致性的一种要求。

我们希望,在维护长周期软件的时候,代码具有一致性,无论由多少人共同编写而成,看起来都应该像一个人写的。这样,沟通成本是最低的,知识的传递成本也是最低的。Readability 培训,就是希望告诉我们的工程师,什么样的代码是可读可测的,设计良好的,确保编程一致性,降低随机复杂性。

这个图是另一个引入随机复杂度的例子。大家知道,依赖越多,复杂度就越大。

这是 golang 语言程序的 go.sum 文件。

左边的版本一共有95行,也就是 95 个依赖包,这些包里面有很多重复,一些在小版本号上有细微的差别。

右边是功能不变,但重构后的版本。依赖只有 55 行。也就是通过可读可测性改造后,依赖可以降低约 50 %。

5 Everything as Code

另一个一致性的例子是 Everything as Code。

在上面这个图中,使用声明式描述来表达某个服务的监控要求。它语义表达明确,与代码的表现形式相同,且存储在代码库中。

好处是:「它的变更与代码变更一样,可以做 Code Review ,可以自动记录版本变更。还可以使用编程方式对其进行自动化测试与验证」。

方便团队对整个基础设施进行统一治理,这是非常关键的,它可以做规范性管理,做自动化检查,如果发生了变更,比如监控系统 从 A 平台 变成了 B 平台,只要业务领域中的这些警告没有发生变化的话,那么,B 平台就可以很轻松自动对这些配置进行迁移,并不需要业务方登录平台,重新配置一遍。这样,我们可以消除很多琐事。

上面这图是 Google Borg 所用的配置文件。谷歌在代码管理上要求有强一致性,所以,其服务的文件目录结构是完全一样的,其中,每个服务的目标下都有一个 Borg 配置文件目录,描述该服务是如何配置的。这样的话会带来什么好处呢?可管理,可治理。

…… ( 这里省略一大段) ……

现在,有了 Pipeline as Code,使得规模化的管理成为可能。最重要的,它能够完成组织的统一治理工作。规模越大,组织治理的工作就越多,这些都会带来成本,我们必须想办法减少组织治理成本,把节省下来的精力投入都创造真正的价值上面。

6 工具平台要面向真正的工程师,而不是「码农」

这一页是讲如何做好面向「工程师」的 DevOps 工具平台。

左边这是我们原来做工具的理念或状态,右边是我们应该走的方向。

我们必须在一定的方法论指导下,让我们的各种工具平台也应该具有一致性,即使是百花齐放,也要在一致性的约束之下。

当然,前面已经说过,整个组织就是一个复杂系统。在做研效提升工作中,可能需要涉及到多个方面的改进。很多方面相互关联,牵一发而动全身。

7 达成代码一致性,将是一次组织级的质变

除了上面提到的流程与工具平台,负责技术管理的人员也要赋予开发工程师必要的时间去做好设计,写好代码、实现自动化测试,做好 CodeReview 、做重构,以保持我们的代码资产处于相对良好的水平,从而确保研发效能不降低。

上图中是 Chromium 对一个只有 33 行的代码变更做的 CodeReview 过程。

这个过程用于十天的时间。这个效率放在很多公司来看,实在是太低了。如果你这么想,你应该还没有做到代码的一致性。当然,这也是最难的一部分。

然而,一旦做到代码一致性,将是一次组织质变。

那么,在我们的组织中是否需要建立这种一致性呢?这就要由管理者来回答了,而不是一线工程师需要回答的问题。因为,当你回答这个问题的时候,就意味着要投入相应的时间、精力、资源、成本。

8 度量是一定要做的

既然我们谈到研效提升,就一定会涉及到度量。德鲁克说:没有度量,就无法管理。

在这里,想问一下大家:有没有哪个研发团队被称赞开发速度越来越快的?反正我是没有听说过。我干了这行近 30 年,从来没听说过这样的事情,只听到过有人抱怨开发速度越来越慢。

当然,我们几乎也没有建立过任何衡量体系,别人怎么会知道开发速度慢了呢?只能是靠感觉。但感觉的事情并不靠谱,必须要找到一定的实证,否则的话都不作数。我们改进也是一样的。

因为时间关系,这里就不展开度量这个话题了。我刚才已经讲过为什么要有一致性,以及一致性要花一定的成本。

9 组织如何能达成「一致性」

如何达成一致性?达成一致性,可以按“道、法、器、术”这个四个方面来考虑。

道,是指工作中所秉承的工作思想、理念和方法论。只有在相同方法论的指导下,才能达成工作中的一致性,否则大家会有不同的理解。

在方法论一致的情况下,必须能够将方法论具象化为工作中的具体要求与规范,这就是“法”。

在此基础之上,才能有力支撑“器(工具平台)”和“术(具体实践)”之间的一致性。

如果没有“道”与“法”,而仅仅关注“器”与术,那么,在多团队协作过程中,很难达成“一致性”。

注意:器与术并不是先后关系,而是并列关系。

研发效能提升的“道”,我认为应该是“持续交付2.0的双环模型”,它包含了重要的指导思想和一系列的工作基本原则。

「 EPC 」就是由它派生的要求与规范。其中,每个维度都有对应的规范,其中讲解了“是什么,做什么,怎么做”。

工具建设和具体实践应该理解和遵从这些“规范”。

我们的软件越成功、场景越多,参与协作的人员就越多,复杂性就越高。如果我们想让效率不降低,就要想办法消除随机复杂性,而消除随机复杂性的必经之路就是“一致性”。而要达成一致性,大家必然要遵守相同的“道、法、器、术”。

10 工程师的五件事

作为一名工程师,他日常工作包括五件事情。

  • 一是“救火”,线上出问题要马上修复。
  • 二是“琐事”,很多重复性的手工操作,比如手工建立流水线,手工回归测试。
  • 三是“创造”,就是开发新的有价值的特性。
  • 四是“改善”,包括改善流程,工具,提升个人能力。
  • 五是“开销”,例如参加例行的周会,月会等等,一些管理事务的开销。

我们要通过不断投入做改进,尽可能消除“救火”,减少“琐事”,最大限度的降低“开销”,从而节省更多的时间用于创造价值。

那么,如何落实到日常执行工作中呢?通过迭代抽象,写好代码,写清文档,从而在整个系统内达成一致性。只有这样,即使规模变大,整个系统也可以做到简单而可靠。

最后,如果你暂时无法将我刚才讲的“一致性”结合你的实际情况,还没有想清楚怎么做,但仍想动手改进的话,我的建议是:

渐进式改进的初期可以使用“ CLCT ”作为北极星指标。

Change List 就是一次代码变更,也是每个开发工程师的最小工作单位。

CLCT ( Change List Cycle Time ),就是从代码提交到上线的这段时间长度。

因为影响 CLCT 的因素最少,其确定性和可预期性最高,所以,可以用做在改进初期对效率改进的一个关键指标。

它度量的本质其实是从“开发完成”到“真正完成”之间的效率。它越小,说明团队中的琐事就越少。

好,因为时间关系,我今天就讲这么多,讲的有点儿快,有些内容没有展开,请大家包涵。

最后,谢谢大家的踊跃参与。