一、何为研发效能?

当我们谈研发效能的时候,我们在谈些什么?这个议题被抛出来,有人讨论,是因为存在问题,问题就在于实际的研发效率,已经远低于预期了。企业初创的时候,一个想法从形成到上线,一个人花两个小时就完成了,而当企业发展到数千人的时候,类似事情的执行,往往需要多个团队,花费好几周才能完成。这便造成了鲜明的对比,而这一对比产生的印象,对于没有深入理解软件工程的人来说,显得难以理解,可又往往无计可施。

细心的读者会留意到,前文我既用了“效能”一词,也用了“效率”一词。这是为了做严谨的区分,效能往往是用来衡量产品的经济绩效,而效率仅仅是指提升业务响应能力,提高吞吐,降低成本。

这里的定义引用了乔梁的《如何构建高效能研发团队》课程材料,本文并不讨论产品开发方法,因此后面的关注都在“效率”上。

本世纪 10 年代,早期的互联网从业者开发简易网站的时候,只需要学会使用 Linux、Apache、MySql、PHP(Perl)即可,这套技术有一个好记的名字:LAMP。可今天,在一个大型互联网公司工作的开发者,需要理解的技术栈上升了一个数量级,例如分布式系统、微服务、Web 开发框架、DevOps 流水线、容器等云原生技术等等。如果仅仅是这些复杂度还好说,毕竟都是行业标准的技术,以开发者的学习能力,很快就能掌握。令人生畏的复杂度在于,大型互联网公司都有一套或者多套软件系统,这些软件系统的规模往往都在百万行以上,质量有好有坏(坏者居多),而开发者必须基于这些系统开展工作。这个时候必须承担非常高的认知负荷,而修改软件的时候也会面临破坏原有功能的巨大风险,而风险的增高就必然导致速度的降低。

因此研发效率的大幅降低,其中一个核心因素就是软件复杂度的指数上升。

二、本质复杂度和偶然复杂度

Fred Brooks 在经典著作《人月神话》的「没有银弹」一文中对于软件复杂度有着精彩的论述,他将软件复杂度分为本质复杂度(Essential Complexity)和偶然复杂度(Accidental Complexity)。这里的本质和偶然两个词来源于亚里士多德的《形而上学》,在亚里士多德看来,本质属性是一个物体必然拥有的属性,偶然属性是一个物体可以拥有的属性(也可以不拥有)。例如,一个电商软件必然会包含交易、商品等业务复杂度,因此我们称它们为本质复杂度;而同一个电商软件,可以是基于容器技术实现(也可以不是),可以是基于 Java 编写的(也可以不是),因此我们称由于容器技术或者Java 技术而引入的复杂度,为偶然复杂度。

Fred Brooks 所描述的软件本质复杂度,指的就是来自问题域本身的复杂度,除非缩小问题域的范围,否则是无法消除本质复杂度的。而偶然复杂度是由于解决方案带来的,例如选择了 Java,选择了容器,选择了中台等等。

此外,我们可以从所谓问题空间(Problem Space)和方案空间(Solution Space)来理解这两个复杂度,问题空间就是现实的初始状态和期望状态,以及一系列约束规则(我们常常称之为业务),方案空间就是工程师设计实现的,一系列从初始状态达到期望状态的步骤。缺乏经验的工程师往往在还没理解清楚问题的情况下就急于写代码,这便是缺乏对于问题空间和方案空间的理解,而近年来领域驱动设计为那么多工程师所推崇,其核心原因就是它指导了大家去重视问题空间,去直面本质复杂度。Eric Evans 在 2003 年的著作《Domain Driven Design》,其副标题是 “Tackling Complexity in the Heart of Software”,我想这也不是偶然。

《人月神话》写于 1975 年,距今已经有 47 年了,Brooks 认为软件的本质复杂度是无法得到本质上的降低的,同时认为随着高级编程语言的演进,开发环境的发展演进,偶然复杂度会得到本质的降低。他的论断前半部分对了,然而后半部分是错了,我们今天的确有更高级的编程语言,功能更丰富的框架,能力更强大的 IDE,但是大家逐渐发现学习这些工具已经成为了一个不小的负担。

三、复杂度的爆炸

软件只要不消亡,只要有人用,有开发者维护,那么它的复杂度几乎必然是不断上升的。软件的生存发展意味着商业上的成功,随着时间的积累,越来越多的人使用它,越来越多的功能被加入进去,它的价值越来越大,给企业带去源源不断的收入。前面我们解释过,软件的本质复杂度实际上是问题空间(或者称之为业务)带来的,因此给软件加入越多的功能,那么它就必然会包含越多的本质复杂度。此外,每解决一个问题,就对应了一个方案,而方案的实现必然又引入新的偶然复杂度,例如为了实现支付,需要实现某种新的协议,以对接一个三方的支付系统。软件复杂度是在商业上成功的企业所必须面对的幸福的烦恼。

和Brooks的时代所不同的是,今天的软件已经从深入到人类生活的方方面面。稍有规模的互联网软件,都服务着数百万、千万级的用户。阿里巴巴的双11在2020年的峰值实现了每秒58.3万笔的交易;Netflix 在2021年Q4拥有了2.2亿的订阅用户;而 TikTok 在2021年9月宣布月活数量超过10亿。这些惊人的商业成功背后,都少不了复杂的软件系统。而所有这些复杂软件系统,都不得不面对巨大的 Scalability 的挑战,服务一个人的系统,和服务一亿人的系统,其复杂度有着天壤之别。

本质复杂度是一个方面,毕竟更多用户意味着更多的功能特性,但我们无法忽略这里的偶然复杂度,其中最典型的就是分布式系统引入的偶然复杂度。**为了能够支撑如此大规模的用户量,系统需要能够管理数万机器(调度系统),需要能否管理好用户的流量(负载均衡系统),需要能够管理好不同计算单元之间的通讯(服务发现,RPC,消息系统),需要能够保持服务的稳定性(高可用体系)。**这里的每一个主题都能延展开用几本书来描述,而开发者只有在初步掌握了这些知识后,才能够设计实现足够 Scalable 的系统,服务好大规模的用户。

相比于分布式系统引入的复杂度,团队的扩张更易带来偶然复杂度的急剧增长。成功产品的软件研发团队动辄数百人,有些已经达到了一两千人的规模。如果企业没有严格清晰的人才招聘标准,人员入职后没有严格的技术规范培训,当所有人以不同的风格,不同的长短期目标往代码仓库中提交代码的时候,软件的复杂度就会急剧上升。

例如,团队成员因为个人喜好,在一个全部是 Java 体系的系统中加入了 NodeJS 的组件,当该成员离开团队后,这个组件对于其他不熟悉 NodeJS 的成员来说,就是纯粹多出来的偶然复杂度;

例如,团队新人不熟悉系统,为了急于上线一个特性,又不想影响到系统的其他部分,就会很自然地在某个地方加一个 flag,然后在所有需要改动的地方加 if 判断,而不是去调整系统设计以适应新的问题空间;

例如,同一个领域概念,不同的人在系统不同的模块中使用了不同的名字,核心内涵完全一致,但又加入了差异的属性,平添了大量理解成本。