软件工程没有银弹,是一个古老的话题,来自于在1987年的论文《No Silver Bullet - Essence and Accidents of Software Engineering》(见参考文档),另外在我读大学时读《人月神话》时作者也再次强调了“没有银弹”的观点。
尽管没有银弹,但是我觉得持续集成CI中的一个理念 - 缩短反馈回路,有点类似于头孢、青霉素这样的“广谱抗生素”,可以适用于很大一类软件工程项目。
我作为一个开发,改了改了一行代码、改了一个配置等,是可以很快速地得到反馈,我的修改是否正确,至少没有影响到软件系统现有的功能、性能。
缩短反馈回路包括两方面:
1. 什么时候要反馈都有可以,不需要等待
2. 反馈本身的时间要足够短,结果立等可取
当然,缩短反馈回路 或者说 去建设持续集成CI系统 是需要成本的,需要投入人力和时间去写自动化用例、建设基础设施、看护CI的日常运行状态。但是我认为,这里的投入,是能在其他地方赚回来。 这就要说到,自动化也好、持续集成也好,它的价值体现了,我的观点很朴素:一个CI的价值与其被运行的次数成正比(单个自动化用例也类似)。如果预估到这个CI未来要运行成千上万次,那么就应该毫不犹豫投入去建设好它,而如果未来预期中只有几次重复执行的机会,那么这个CI建设的优先级就会很低。
我把CI在软件工程中的流程顺序分为:单元测试UT、单机集成CI、系统间CI、全链路测试或CI。从缩短反馈回来来讲,应该是前面的UT和单机CI最优先建设和运行;而到线上发布后才发现问题去做回滚 就是反馈回路最场的了,我们应该尽量避免。
然后在实际工作中,各种CI的建设顺序却没有一个固定不变的原则,根据业务形态不同、团队人员构成不同、具体项目不同、发展阶段不同,也是有可能先建设系统间的CI甚至全链路的CI的,因为前面建设UT可能要写大量的白盒单测代码(可能代码量是业务逻辑代码的几倍 不一定能得到资源投入;特别是考虑到代码覆盖率的高要求 则投入更大)、单机集成要引入一些去外部依赖的桩模块也需要额外开发工作,而系统间/全链路的CI做好了,至少在客户E2E功能的角度来说还是能有基本保障的。
从最终形态上来讲,我认为是:
从用例丰富度/覆盖度维度来看,单元测试UT > 单机CI > 系统间CI > 全链路CI
从运行次数/频率上来看,单元测试UT > 单机CI > 系统间CI > 全链路CI
从反馈回路的长短上来看,单元测试UT < 单机CI < 系统间CI < 全链路CI
另外 有一个常见问题是:为什么我投入做UT做CI花了时间,让我的工作效率降低了?(往往来自某个被要求写自测代码的开发同学~)
我的理解是:某种程度上来说,这确实是个事实,特别是对某个人来说对他在某个项目某个阶段来说确实是降低了开发效率。但从一个团队一个部门一个产品线的整体来讲,这种投入往往是值得的,因为前置的反馈回路端的CI 拦截了很多的问题,避免流入到反馈回路更长的CI或者故障之中去,从而节省了团队中每个人的时间,保障了产品的整体质量。这种情况,可能还是得从Leader层面达成一致,从上往下去推进;而依赖一线程序员自发去做可能比较难。
个体做出一些付出,往往让团队在整体上受益,这样的例子是很多的。比如:在公路上开车,对某一个人来讲,他胡乱变道、到处加塞,可能会提高他个人到达目的地的效率,但如果人人像他这么做,势必交通秩序荡然无存、造成混乱、造成一些交通事故,反而让整个道路上的整个通行效率大大降低,损害了整体的利益。
当然,就这个问题而言,对于这个写自测代码很多的开发同学给予一定程度的表扬和绩效倾斜可能是有一定必要的,这样才能组件形成良好的氛围和正向的反馈。
参考文档:
没有银弹,原论文: https://ieeexplore.ieee.org/document/1663532
《人月神话》中的“没有银弹”:https://github.com/zhengda/The-Mythical-Man-Month-zh/blob/main/docs/ch17.md