在学校时,你学习编程,写写课程作业的代码,但你想过真正的行业里,公司中的规模化开发方式是怎样的吗?在上一篇《核心:安全与效率》的文中,你应该还记得我讲的那个电站的例子,那么编写课程作业的代码就像搭建的 “酒精灯电站”,而工业级的规模化开发才是建设 “真实电站” 的方式。

工业级规模化的程序系统开发包括了一系列的过程,而这一系列过程的起点是:需求。

需求与调度

需求,有时会有很多不同的表达形式,包括:客户的诉求、用户的请求、老板的要求,但这些不同的表达形式,不一定是真正的需求。

客户的诉求,更多来自传统甲、乙方关系的场景,在软件工程过程中有一个子过程——需求工程——去对客户的诉求进行分析和提炼,并转化为需求文档。用户的请求,更多来自互联网 toC 的场景,通过洞察大量用户请求中的共性去提炼并转化为真正的产品需求。老板的要求,更多是因为老板也可能是你的产品用户之一,但这个用户的特殊之处在于,他为这个产品买单。所以,他的要求无论合理与否都能很容易地变成需要开发的需求。

正因为需求的来源多,表达形式也多,因而真实情况是 “需求” 似乎总是源源不绝,但是真正的需求往往隐藏在这些诉求、请求与要求的表象之下。这是关于 “需求” 的第一个困难点。如果我们总是能找出真正的需求,那么也许需求也就没那么多了。但现实往往是我们不能,那么需求过载的场景就会常常发生。

这时,你就会面临第二个困难,如何对过多的需求进行排序?

为什么需要排序?我们进行需求排序的原因是,在有限的资源下我们想要达到如下目标:

只有当用户需求被快速地满足时,他们才会感到满意。但在有限资源限制的条件下,我们不可能让所有用户的需求都能被快速满足。面对这样的矛盾,我们也许可以学习、借鉴下操作系统的资源调度策略

我用了好多年的 Mac 和 iPhone,发现它们相对同等资源配置的 Windows 和 Android 机,在满足用户使用的响应性方面要好得多,特别是在使用了几年之后,这种差距会更明显。

在同等硬件资源配置的情况下,Mac 和 iPhone 表现得更好,只可能是其操作系统的资源调度策略实现更优秀。通常,操作系统需要同时执行多个应用程序时,它的执行调度策略就会在多个应用程序间不停切换,有如下几种常见的调度策略:

  1. 先来先执行
  2. 执行起来最快的先执行
  3. 占用资源最少的先执行
  4. 释放资源最多的先执行
  5. 高优先级的先执行

当资源充足,只用策略 1 就足够了,但更多情形下需要综合后 4 种策略。比如:老板的要求天生自带高优先级,需要先执行;而一些小需求,优先级不高,但执行快,占用资源少,随着它们排队的等待时间延长,优先级可以逐步提升,以免消耗完用户的等待耐心,形成负面评价。

当用户同时运行的应用程序确实太多时,操作系统发现资源无论如何调度都不够了,它有一个选项是提供了资源消耗的监视器,来提示用户主动停掉一些同时运行的应用,而最后的底线是操作系统主动杀掉一些应用程序以释放资源,以保障系统还能正常地运转下去。那么我们在调度需求时,是否也能以透明的资源消耗监视提示用户主动控制需求或选择 “杀” 掉需求,然后还不降低用户的满意度呢?

需求调度,可以像操作系统一样运转,形成一个规模化的需求调度体系,并通过多种调度策略来平衡需求的响应性和投入产出的价值最大化。

设计与开发

紧接需求之后的过程是:设计与开发。

成为程序员后,你一开始可能会习惯于一个人完成系统开发,自己做架构设计、技术选型、代码调测,最后发布上线,但这只适合代码量在一定范围内的系统开发模式。在一定范围内,你可以实现从头到尾“一条龙”做完,并对系统的每一处细节都了如指掌,但当系统规模变大后,需要更多的人共同参与时,整个设计和开发的模式就完全不一样了。

一定规模的系统,下面又会划分子系统,子系统又可能由多个服务构成,而每个服务又有多个模块,每个模块包含多个对象。比如,我现在所在团队负责的产品,就由数个系统、十数个子系统、上百个服务构成,这样规模的系统就不太可能光靠一个人来设计的,而是在不同的层次上都由不同的人来共同参与设计并开发完成的。

规模化的设计思路,一方面是自顶向下去完成顶层设计。顶层设计主要做两件事:

今年世界杯期间,读到万维钢一些关于 “足球与系统” 的文章,感慨原来系统的顶层设计和足球运动十分类似。按文中所说,足球的科学踢法是:“球员必须建立 ‘区域(zone)’ 的观念,每个球员都有一个自己的专属区域”,通过区域的跑位来形成多样化的传球路线配合。

而系统的区域划分,也是为了让系统内部各部分之间的耦合降低,从而让开发人员在属于自己的区域内更自由地发挥。而在区域内的 “控球”“传球” 与 “跑位”,就更多属于开发人员个体能力的发挥,这个过程中区域的大小、边界都可能发生变化,进而导致区域之间的通信路径也跟随变化。这样的变化,就属于自底向上的演化过程。

所以,规模化设计思路的另一面,就是要让系统具备自底向上的演化机能。因为,自顶向下的设计是前瞻性的设计,但没有人能做到完美的前瞻性设计;而自底向上的演化机能,是后验性的反应,它能调整修复前瞻性设计中可能的盲点缺陷。

记得,我曾经看过一个视频名字大概是 “梅西的十大不可思议进球”,视频里的每一个进球都体现了梅西作为超级明星球员的价值,而在前面提及的万维钢的文章中,有一个核心观点:“普通的团队指望明星,最厉害的球队依靠系统”。其实二者并不矛盾,好的系统不仅依靠 “明星” 级的前瞻顶层设计,也指望 “明星” 级的底层演化突破能力。

所以,一个规模化的系统既要靠前瞻的设计视野,也依赖后验的演化机能,这样才可能将前瞻蓝图变成美好现实。

测试与运维

完成了设计与开发之后,系统将进入测试,最后发布上线进入运行与维护阶段。

在前面需求、设计与开发阶段的规模化过程中,都存在一个刚性扩展的问题,也就是说,如果提出的需求数量扩大一倍,那么需要去对接、分析和提炼需求的人员理论上也要扩大一倍;如果提炼出的需要进入开发的需求也翻倍,相应开发人员只增长一倍那已经算是理想情况了,这说明系统的正交与解耦性做得相当完美了,所有的开发都能并行工作,不产生沟通协调的消耗。

但真实的情况不会那么完美,需求的产生与爆发很可能是一种脉冲式的,而企业一般会维持满足需求平均数量的开发人员,当需求进入脉冲高峰时,开发资源总是不够,然后就会过载,进入疯狂加班模式。

开发完成后,进入测试与线上运维的循环阶段,这个阶段与前面阶段的不同之处在于:需求提炼、设计开发基本都只能由人来完成,而测试、运维的很多步骤可以通过制作工具来完成自动化过程。所以,这个阶段随着规模化过程实际可以是一个柔性扩展的阶段。

但它从来不是一开始就是柔性的,因为制作工具也有一个成本考虑。比如,在我做的这个系统发展的早期,系统架构简单、部署规模也很小,基本所有的测试与运维工作都是通过人肉来完成的,这样的效率也不算低,唯一的问题是对测试人员而言,大量的工作都是低水平的重复,不利于个人成长。

随着后来的业务快速增长,系统增长越过某个规模临界点,这时人肉负载基本饱和,效率已没法提升,制作工具是唯一的出路。你可能或多或少都知道一些现代化的工业流水线,而在软件开发过程中,“测试与运维” 的运转体系是最可能接近工业流水线方式的。

因此,以测试为例进行规模化的最佳方式,就是打造一条 “测试机器” 流水线,而我在《转化:能力与输出》一文中写到了关于打造 “机器” 的三个核心点,这里再强调一次:

围绕这三个核心点,我们再来看看 “测试机器” 如何打造?

从开发提测,机器自动下载提测版本分支代码,进行构建编译打包,实施代码规范性检查测试,通过后发布测试环境,进行分层次的各类自动化专项测试。如:用户终端层、通信协议层、服务接口层、数据存储层的各项测试,全部通过后,生成相应的测试报告,进入下一步发布流程。这就是测试体系的“流程”,而“规则”就是其中定义的各种测试项检查约束。

上述流程中涉及的“工具系统”包括:代码规范检查工具、终端 UI 的自动化测试工具、通信协议与服务端接口调用的模拟工具、数据一致性校验工具、测试报告生成工具、测试 Bug 统计分析与收敛趋势等可视化展现工具,等等。

最后,“规范共识” 是整个团队对这个流程环节、里面具体规则的定义以及 Bug 分类等方面达成的共识,这决定了这台 “测试机器” 运转的协调性与效率。

测试通过后,发布到线上就进入了运维阶段,行业里已经有大量的关于 DevOps 的分享内容,而它的本质也就是打造了一台 “运维机器” 流水线,这和我上面描述的 “测试机器” 运转类同,只是有不同的规范共识、流程规则和工具系统,便不再赘述了。

到了规模化的测试与运维阶段,看一个团队的水平,就是看这台 “机器” 的制作水准与运转效率。

在程序系统的开发过程中,当系统的大小和复杂度到了一定的规模临界点,就会发生从量到质的转变,规模不同,相应的需求调度、设计开发、测试运维的过程也都不同了。

量级变了,逻辑就不一样了。

每一个具备一定规模的组织都有对应的规模化工程过程,而且这个过程的形成受公司文化、团队构成、组织架构,甚至业务特性共同决定。那你所在组织的规模化过程是怎样的?这个过程系统如何运作的?欢迎你在留言区分享。


评论