在当今数字化时代,软件研发行业的创新和实战已经成为企业发展的重要支撑。
然而,随着软件规模的不断增加和复杂度的不断提升,软件研发面临着越来越多的挑战。
在这样的环境下,如何保持创新和实战的活力,成为软件研发行业必须面对的问题。
更重要的是,进入2023年,面对愈加复杂多变的外部环境,如何高效实施降本增效战略也成为各IT企业更为急迫的问题,即在有限的资源条件下,如何去追求更高的效率、更高的质量、更好的用户体验和更强的创新能力。
《软件研发行业创新实战案例解析》一书为此给出了丰富的行业案例,本文就先来初步探讨一下软件规模和复杂度困局的破解之道!
01
从Google的一页PPT开始谈起
记得曾经参加过一个软件工程的会议,其中一个话题来自Google,嘉宾PPT的第一页给我留下了深刻的印象,大概的样子如图1所示。
图1 别人眼中的Google和Google人眼中的Google
别人眼中的Google是各种高科技的东西,而Google人眼中的Google,却是老牛拉车。
为什么会出现这种情况呢?当然,其中有Google谦虚的一面,更重要的是Google看清了当今软件研发的关键问题:规模和复杂度。
02
软件研发永远的痛:规模与复杂度
如果你认真思考一下,就会发现软件研发本质上属于“手工业”。
虽然“个人英雄主义”的石器时代已经结束,目前人们处于群体协作时代,但本质上依然没有摆脱“手工业”的基本属性。因此,软件研发在很大程度上还是依赖于个人的能力。当软件规模较小时,依赖“手工业”可以解决问题,但是当软件规模大了之后再依赖“手工业”就不行了。
当软件研发团队规模较小时,一个想法从产生到上线,一个人可能就花半天时间。而当软件研发团队发展到数百人时,执行类似的事情往往需要跨多个团队,花费几周时间才能完成。
由此可见,随着时间的推移和团队的发展壮大,软件研发的效率大幅降低,其中一个核心因素就是软件研发规模的扩大和复杂度指数的上升。
软件规模与软件复杂度的关系类似于人的身高与体重的关系。身高为90cm的孩子的体重大概是15kg,180cm时的体重大概是75kg,身高增加了1倍,体重却增加了5倍。我们可以将软件规模类比成身高,将复杂度类比成体重,那么软件规模的扩大必然伴随着软件复杂度更快的提升,如图2所示。
图2 软件复杂度提升示意图
03
软件复杂度困局
针对传统事务,复杂问题一般都是肉眼可见的,可以及时找到处理和应对的方法,也能分清相关的责任。但是,软件研发是知识手工业者的大规模协作,随着需求的演进和设计的迭代,各种由复杂度产生的风险只是从眼前分摊出去,再在看不见的地方集聚起来。
在这个过程中,每一个具体环节都在做看似正确的事,每一个具体的人都在推动项目的完善。但是整体的结果是,不仅累积了巨大的复杂度风险,而且再也找不到具体的人、具体的环节来对整个软件系统负责。因此,结果是不仅风险大,而且治理起来也特别困难。
软件的复杂度包含两个层面:软件系统层面的复杂度和软件研发流程层面的复杂度。
在软件系统层面上,针对大型软件,“when things work, nobody knows why”俨然已经是一种常态。随着时间的推移,现在已经没有人清楚系统到底是如何工作的,将来也不会有人清楚。
在软件研发流程层面上,一个简单的改动,哪怕只改动一行代码也需要经历一次完整的流程,涉及多个团队和多个工具体系的相互协作。
可以说,对于大型软件来讲,复杂才是常态,不复杂才不正常。
那么,为什么要将大型软件做得这么复杂?复杂度又从何而来?
04
软件研发的复杂度从何而来
软件系统很难一开始就做出完美的设计,只能通过功能模块的衍生迭代让软件系统逐步成型,然后随着需求的增加再让功能模块进行衍生迭代,因此本质上软件是一点点生长出来的,其间就伴随着复杂度的不断累积。
是的,你没有听错,软件是生长出来的,而不是设计出来的,如图3所示。
图3 软件生长示意图
无论看起来多么复杂的软件系统,都要从第一行代码开始,都要从几个核心模块开始,这时的架构只是一个少量程序员就可以维护的简单组成。
那么,你可能要问:软件架构师是做什么的?难道他们不是软件的设计者吗?其实,软件架构师只能搭建软件的骨架,至于最终的软件会长成什么样子,他们也很难知道。
软件架构师和建筑架构师有着巨大的差异。
只要建筑图纸设计好了,材料、人力、工期和进度基本就能确定,而且设计变更往往只发生在设计图纸阶段。也就是说,建筑架构的设计和生产活动是可以分开的。
软件的特殊性在于,“设计活动”与“制造活动”彼此交融,你中有我,我中有你,无法分开,软件架构只能在实现过程中不断迭代,因此复杂度一直在不断积累。
另外,建筑架构师不会轻易给一个盖好的高楼增加阳台,但是软件架构师却经常在做类似这样的事,并且总有人会对你说,“这个需求很简单,往外扩建一些就行了”。这确实不复杂,但我们面临的真实场景往往是:没人知道扩建阳台后原来的楼会不会开裂,甚至倒塌。
《从工业化到城市化》这本书中提出了一个很有洞见的观点:“工业是无机体,可以批量复制,而城市是有机体,只能慢慢生长”。
“工业化可以被看作一个‘复制’的过程。可以想象一下复印店里的复印机,只要有机器、有原件、有纸和墨,就能开始一张张地复印,速度是非常快的。工业化也是类似的,有了技术、资金、劳动力这几个条件,就可以进行大规模的工业生产。但是城市化就不是一个能快速‘复制’的过程,而是一个需要‘生长发育’的过程。城市不仅是钢筋水泥、道路桥梁,更是一套复杂的网络,城市中的生活设施、消费习惯、风土人情等,这些都需要一定的生长时间。”
笔者认为建筑架构更像是工业化的无机体,可以非常规整,而软件架构更像是发展中的城市,需要时间的洗礼,其复杂度和不确定性特别高。因此,维护大型软件的关键是控制复杂度。
注意,我们能做的只是延缓复杂度的聚集速度,无法完全杜绝复杂度的提升。为此,我们要深刻理解软件的复杂度。
05
软件复杂度的分类
软件复杂度的分类如图4所示。
图4 软件复杂度的分类
本质复杂度是软件必须拥有的,继承自问题域本身的复杂度,除非缩小问题域的范围,否则无法消除本质复杂度,本质复杂度是系统复杂度的下限。
随机复杂度是软件可以拥有也可以没有的属性,由解决方案的实现过程附加产生,主要表现为短视效应、认知负荷和协同成本,是我们需要尽力规避的部分,也是需要关注的重点。
06
随处可见的随机复杂度
下面通过案例说明随机复杂度的表现形式。
案例1:如图5所示,服务A和服务B调用服务S,开始的时候一切正常,相安无事。后来,增加的服务C也调用服务S,这时发现服务S有一个实现上的缺陷。此时,理论上应该修改服务S,但由于负责服务S的团队怕影响其他现有服务,因此缺乏解决该问题的动力,或者由于负责服务A的团队正忙于其他新特性的研发,因此服务C的团队不得不“曲线救国”,在自己的服务C中实现变通。一段时间以后,服务B也发现了服务S的缺陷,同样也是自己采用了变通方法。但是服务B和服务C采用的变通方法可能并不相同,这就为以后的维护挖了“坑”,这些都是在积累系统的随机复杂度。
图5 随机复杂度的表现形式案例
案例2:团队成员因为个人喜好,在一个全部是Java体系的系统中加入Node.js的组件,这对于其他不熟悉Node.js的成员来说,就是纯粹多出来的随机复杂度,而且一旦引入后面再想去掉就难了。
案例 3:团队的不同成员为了快速实现通用功能,使用了能实现相同功能的不同组件,或者即使使用了相同的组件,但是使用的组件版本也各不相同,这种不一致性也直接产生了本不应该存在的随机复杂度。
案例 4:团队新人不熟悉系统,但急于实现一个新特性,又不想对系统其他部分产生影响,就会很自然地在原有代码的基础上添加if-else判断,甚至直接复制代码并在复制的代码上做修改,而不是去调整系统设计以适应新的问题空间,这种做法看似“短、平、快”,实则引入了随机复杂度,为以后的维护挖了“坑”。
案例5:缺乏领域建模,同一个业务领域概念在不同模块中使用了不同的命名,但是领域内涵完全一致。更糟的是,在不同模块中的实现又不同,各自还加入了差异的属性,这样,后续对模块的理解和维护成本都会变得更加复杂。
案例 6:由于项目时间紧张,因此设计的变更直接在代码上修改,使得设计文档和实现不匹配,这也是增加随机复杂度的一个重要因素。
类似的例子,笔者相信你们可以列举更多。
随机复杂度是我们需要重点关注的,其中的短视效应表现为急功近利,这种做法会快速增加系统的技术债务使架构腐化加速,由此造成后续研发认知负荷的增加,更多的协作也会造成协同复杂化,进而降低研发效能。当研发效能降低时,工程师就更倾向于使用急功近利的奇技淫巧来实现交付业务,最终形成恶性循环。
07
失控的软件复杂度
软件复杂度失控的原因是多方面的,下面罗列了其中最重要的几条。
(1)软件复杂度失控是商业上成功的企业必然面对的“幸福的烦恼”。
随着企业业务的发展,软件也在不断“生长”,在这个过程中软件需要加入越来越多的新功能,这些新功能必然会引入更多的本质复杂度。而且,因为每次加入新功能都是在原有功能的基础上进行的,所以必然又会引入更多新的随机复杂度。
(2)随机复杂度会随着时间不断积累,如果不进行有针对性的治理,积累的速度会越来越快。
一款软件在商业上的成功意味着其软件生命周期就会比较长,那么,除了考虑当前研发团队的复杂度,还得考虑软件系统历史上的所有复杂度。
(3)软件系统在业务上的成功,必然会带来研发团队的扩大,这更容易带来随机复杂度的急剧上升。
当所有人以不同的风格、不同的理解、不同的长短期目标往软件系统中提交代码时,工程一致性的缺失就会使软件的复杂度急剧上升。
(4)重复“造轮子”也是造成复杂度失控的“罪魁祸首”之一。
别人的“轮子”不好用、跨部门沟通成本高、绩效考核需要“轮子”作为“道具”,不同部门重复“造轮子”,同部门的不同团队重复“造轮子”,同组的不同成员也在重复“造轮子”,这些“轮子”除沦落为获得高绩效评价而定向“演戏”的工具外,还为软件系统注入了大量的随机复杂度。
那么,我们应该怎么办?我们要避免“有急乱投医”。
08
常见的错误应对方式
有时候,资源有限未必是坏事,因为它也是倒逼创新的最好方式。但是从短期视角来看软件研发,这个观点似乎并不明智。
最常见的错误方式是采用DDD(Deadline Driven Development,期限驱动开发),用Deadline来倒逼研发团队交付业务功能。但大量的实践经验告诉我们,软件研发就是在需求范围、软件质量、时间进度这个三角中寻求平衡的,如图6所示。
图6 软件研发的三角平衡
短期来看,研发团队可以通过更多的加班来赶项目进度,但如果这个时间限制过于苛刻,那么必然就要牺牲需求范围和软件质量。当需求范围不可裁剪的时候,唯一可以被牺牲的就是软件质量了,这实际上就意味着在很短的时间内往软件系统中倾泻大量的随机复杂度。
而且,上述做法从表面上看可以更快地取得进展,快速摘取成功的果实,但是经过一段时间之后(一般是6~18个月),负面效果就会凸显出来,会显著降低研发的速度和质量。而且这种负面效果是滞后的,等问题能够被感知到的时候,往往已经形成一段时间,软件架构的腐化就是这样在不知不觉中形成的。
以上这种急功近利的做法,本质上是将长期利益让位于短期利益,过度追求短期交付效率,最终的结果只能是“欲速则不达”。
正确战略方向下的“慢”,远远好过错误方向下的“快”。作为技术管理者必须学会两者之间的平衡之道,并为此长期承担后果。
当然,如果你是在创业项目前期,则可以暂且不关注这些,毕竟几个月后你的项目是不是还活着都是一个问题,但如果创业项目熬过了这段时间,还继续这么做就会很危险。
那么,你可能会问创业项目的代码在前期积累了大量的随机复杂度,后续该怎么办?
笔者的答案是,在适当的时候另起“炉灶”,在用户无感知的情况下完成后台服务的替换,这个适当的时候往往就是项目的商业模式完全走通的时间点。正确的技术战略需要能够在宏观层面上帮助系统控制复杂度。
记得在一次行业交流的时候,有一位朋友说到一个观点:“乱七八糟的生机勃勃,好过井井有条的死气沉沉”,乍一听这个观点还是挺有道理的,但是笔者觉得对于软件工程来讲,这个观点是完全不适用的。这个观点对于需要创意的工作是成立的,创意工作一般是单次博弈(前后两次掷骰子没有关联性),而软件工程是工程,属于连续博弈(前面的行为对后面的有影响),所以笔者认为上面的观点不成立。
另一种常见的错误方式是试图通过招聘或借调更多的人来解决软件项目的进度问题。随着项目参与的人越来越多,分工越来越细,人和人之间需要的沟通量也呈指数增长。很快你就会发现,沟通花费的时间渐渐地比分工省下来的工作时间还要多。简单说,过了一个临界点,不是人越多越帮忙,而是人越多越添乱。一个人3个月能完成的事,3个人1个月不一定就能完成,甚至3个月也未必能完成,更何况加入的新人还需要填上认知负荷的“坑”,这些都需要时间成本。
本文分享自 博文视点Broadview 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!