作者:子丑

为什么会有研发规范

很多程序员入职一家新的公司,领完电脑再安装完必备的开发工具,接下来最先接触的恐怕就是新公司的研发规范了。几乎所有的软件企业都有或繁或简的一套或多套研发规范,并且大部分软件团队都认为他们的研发规范是不太一样的,是适合他们当前的实际情况的。

研发规范是什么?它又是如何产生的呢?

我们回看历史,会发现研发规范是伴随着上世纪 60 年代开始的软件危机而产生的。在那个时候,软件由过去个人或者小团队的开发形式,逐渐向规模化、大团队的开发模式转变,软件自身的复杂度变高了,软件研发团队的协作复杂度也变高了,出现了诸如软件项目超预算、交付需求延期、交付质量低下、实现不符合需求等等问题。

软件危机的主要原因,把它很不客气地说:在没有机器的时候,编程根本不是问题;当我们有了电脑,编程开始变成问题;而现在我们有巨大的电脑,编程就成为了一个同样巨大的问题。

— 艾兹赫尔·戴克斯特拉, 谦逊的程序员, 《Communications of the ACM》

为了解决软件危机,软件工程出现了,而研发规范是软件工程实践的组成部分。

研发规范的目标,是为了解决或降低出现软件危机的风险。 其首先要解决的,是随着软件复杂度提高和团队规模变大,所带来的协作低效的问题,比如实现与需求不符、项目难以管理等。

所以,研发规范首先包含了研发过程中的协作规范,比如需求从提出到交付的流程规范、代码从开始开发到应用发布的分支规范等,这些规范本质上都是为了解决软件研发过程中不同团队、不同角色之间的协作问题。除了流程,协作规范还会定义需求的描述规范、代码的提交规范等,通过规范化的方式保证信息的完整性和传递的有效性。

另一方面,软件工程的发展,产生了很多优秀的工程实践,比如实例化需求、测试自动化、代码静态分析等,这些实践有着一定的学习门槛和执行成本,但如果使用得当,则可以帮助提升软件的研发效率和质量。因此,研发规范也会包含必要的工程实践,以规范的形式要求研发人员遵守并执行,一方面降低实践的执行成本,抬高软件研发的效率和质量底线,另一方面通过这种方式提升研发人员的工程 素养。

在某些企业,研发规范会制定得非常详细,包括:

  • 需求管理规范:定义如何收集、记录、分析、验证和管理来自业务和产品的需求,包括需求的模板、需求分析实践、需求流转流程等。
  • 代码管理规范:定义代码库的组织结构、分支策略、提交规范、合并流程、代码规范及如何处理冲突等,也包括代码评审的实践、代码质量的实践等。
  • 制品管理规范:定义制品库的组织结构、制品的存储方式、制品的版本控制、制品的依赖管理以及如何发布和分发制品等。
  • 测试管理规范:定义如何组织和执行软件测试活动,包括所用到的测试手段如单元测试、集成测试、验收测试等,也包括具体的测试实践,如契约测试实践。
  • 自动化测试规范:是测试管理的一个子集,专注于测试自动化的实施,包括自动化测试工具的选型、用例的设计、开发、维护和执行流程等。
  • 生产发布规范:定义如何将软件经过开发、测试,最终部署到生产环境的实践和流程,包括版本的管理、部署流程、发布审批、回滚策略、监控和告警设置等。
  • 服务治理规范:定义服务级别、服务发现、负载均衡、熔断、限流、降级处理等方面的策略和流程,也包括如 AB 测试等工程实践。
  • 安全研发规范:定义整个研发生命周期的安全措施和流程,涵盖需求分析、架构设计、技术实现、测试验收、生产发布等各个阶段,包括需求安全、安全架构、安全编码实践、漏洞管理、密钥和证书管理、合规性检查等方面。
  • 等等

📝 小结: 研发规范以解决或降低出现软件危机的风险为目的,期望通过规范的方式保障软件按时、按质地交付。研发规范包含为解决团队内外研发协同问题而产生的协作规范,和为提升团队工程水平而产生的工程实践规范。

研发规范落地的难点及流水线的局限

距离研发规范的诞生过去了半个多世纪,很多研发团队依旧在与需求交付延期、软件质量低下等问题做着艰苦的斗争。显然,大家在落地研发规范的过程中遇到了困难。难点在哪里呢?

我的观察是,很多团队都能轻易地制定出文档化的研发规范,但研发规范纸面上的精准很难转换为团队工作中的习惯。更难的是,随着业务的发展和团队的变化,研发规范也需要经常更新,如何让大量的研发人员能够及时更新工作实践,以符合新的研发规范,并避免切换期间的混乱,是一个成本很高、又经常被吐槽的事情。

如何让研发规范不只是停留在纸面上?

很多团队想到了将研发规范定义在工具上,期望研发人员只要用这些工具,就必须遵守研发规范。这也是很多企业采购或自研研发工具的初衷之一。在这种模式下:

  1. 企业会将代码、制品等研发资产都托管到代码库、制品库等工具上,并通过分支保护策略、制品晋级机制等手段,将原本定义在文档中的代码规范、制品规范交由代码库、制品库工具来承载。
  2. 另一方面,企业会找到一款流水线工具,按照研发的各个阶段,定义多条流水线,例如:开发流水线、测试流水线、生产流水线,在每条流水线定义好要执行的步骤,比如:单元测试、代码扫描、自动化扫描等。
  3. 最后,企业会将这些代码库、制品库和流水线都定义成模板,后续团队都基于这些模板来创建代码库、制品库和流水线,就自然而然会遵守研发规范了。

下面是我在 2020 年编写的某个基于代码库和流水线的研发规范落地示例。

研发规范(示例):

  1. 任何特性开发都起始于 feature 分支,feature 分支从 develop 分支拉取
  2. feature 只有完成需求开发并通过功能测试和代码评审后,才能合入 develop,进入集成测试阶段
  3. 集成测试阶段验证通过,并获得待发布准入,合入 master,进入发布阶段
  4. 发布阶段必须从 master 分支有新的合入开始,且获得发布准入后,才能进行生产部署
  5. 任何一个阶段失败需要修复,都需要从 feature 阶段重新开始

图片

工具落地(示例):

  1. 在代码库中将 develop 和 master 都设置为保护分支
  2. 分别为 feature 分支、develop 分支和 master 分支各配置一条流水线(见下面 3 张示意图)

图片

开发流水线

图片

集成流水线

图片

发布流水线

这种方式看起来很完美,但执行的时候对工程师的要求仍然较高,比如如果在集成阶段出现一个缺陷,工程师可能会跳过 feature 阶段流水线,直接将改动合并到 develop 上。因此实际落地的效果通常不如预期,且随着时间的推移和规范的更新,只有一小部分规范能被严格执行,其他的又回归纸面了。

造成这种状况的原因是什么?因为研发规范从文档变为工具上的配置的时候,经过了转换和妥协,导致操作对象的变化和关键环节的丢失。即落到工具上的研发规范,跟文档形式的研发规范,已经不一样了,有些概念变了,有些环节丢了,原本完整的规范,经过这一变化,出现了错漏。

我们举几个例子,来看下这种因为工具导致的研发规范妥协的问题:

例子 1:代码提交规范

规范:代码的每次 commit 都应该为解决某一个需求或缺陷服务,commit 的提交信息中都应包含以 #<需求 ID> 的形式定义的需求 ID,且该需求为当前项目中的有效需求。

工具:定义代码库的 push 规则,要求 commit 的提交信息必须符合正则表达式 ^fix #[A-Z]+-[0-9]+。

说明:按照规范,每次提交都要对应一个有效的需求,但是代码库里的检查,却只检查了提交信息是否符合某个格式,对需求的有效性并没有做校验。于是,没有与任何需求有关的 commit 同样可以推送上来,导致这个规范的实践效果打折扣。

例子 2:提测规范

规范:测试只接受自测完成且冒烟测试通过的版本,需求开发完成并满足上述条件,才可提交测试验证。提测时需填写提测单,注明待测的需求、应用版本,并附上自测的报告和冒烟测试的通过报告。如果有新的提交,也需要确保自测和冒烟通过后再流转到测试。

工具:为开发和测试配置不同的流水线,开发流水线执行通过后,开发人员通过 OA 工具或测试平台填写提测表单。测试人员收到提测表单后,按照表单内容准备环境,执行测试流水线。

说明:按照规范,只有经过自测和冒烟的需求,才可提交测试,且需要保证测试验证的版本与开发自测的版本一致,但实际开发和测试在各自不同的流水线里工作,双方的协作依赖第三方的表单或工具,无法保证测试执行与开发自测的是同一个版本。另外,工具也无法确保开发人员有新的提交后,也会遵循通过自测和冒烟再流转到测试的约定。

例子 3:发布准入规范

规范:任何线上发布均需要提交发布单,说明涉及到的需求和应用列表,由团队负责人、安全负责人、测试、运维审核通过后,才可进行发布。

工具:发布工程师通过 OA 工具填写发布单,交由相关人员审批,审批通过后,发布工程师运行生产流水线,如果遇到问题,线下找相关人员修复,再重新运行生产流水线,直到发布完成。

说明:按照规范,未经过发布准入的应用无法执行发布操作,但实际审批与执行是独立的两个工具,由发布工程师自觉遵守发布纪律,保证只有经过审核的发布才能执行,且需自觉保证实际发布的范围与审批的范围一致,比如该合到发布分支的代码都合入了,且没有未经审核的需求被带进来。

上面的 3 个例子体现了工具对研发规范的 2 种形式的限制:

  1. 关键验证点因为数据未联通或联通成本高,导致无法有效验证,如例子 1:代码提交规范。
  2. 流程的两个相邻阶段因为在工具上各自独立,无法线上自动连接,如例子 2:提测规范和例子 3:发布准入规范。

尝试基于流水线来解决决上述问题

一开始,我们尝试基于流水线来解决这些问题,以提测的场景为例,我们尝试过 2 种方法:

1. 用一条流水线串联开发到测试的整个流程

图片

这种方式下,每次代码提交都可能会触发整个流程的执行,但大部分都是不需要提测的。当某个时间点,开发完成,需要手动点击提测按钮,进入到流水线的后半部分。这就导致了流水线运行历史中存在大量的阻在提测步骤的记录,且每次有新的执行都需要重新提测。

2. 让开发流水线完成后自动触发测试流水线

图片

这个方案通过自动触发的方式,将两条独立的流水线串联了起来,但是不触发测试的时候开发流水线的结束状态就比较奇怪了。可能是失败,也可能是跳过,都不太合理。另外,测试流水线仍然是可以独立运行的,因此无法控制只有前序开发流水线通过后,才能运行测试流水线,需要依靠团队的自觉。

这两个方法都未能完整解决提测场景的问题,其它的几个场景也类似。这个时候,我们开始认识到,流水线的定位是一个工具,而我们想定义的是一个解决方案,两者之间在关注点上存在差距,我们得跳出流水线工具,在 DevOps 层面重新思考这个问题。

📝 小结: 工具决定了研发规范的落地方式,而对于工具的妥协也影响了研发规范的落地效果。流水线受限于工具的定位,无法解决研发规范的落地问题,需要在更高的层面来解决。

Aone 带来的启发

阿里内部有个自研的研发工具平台叫 Aone,数万人在上面进行研发协作。之前云效和 Aone 由同一个团队负责,两者产品模型有差异,但很多基础能力是通用的。在商业化的初期,云效推出过一个 RDC 的版本,在模型上与 Aone 非常像,但也正因为与 Aone 的相似性,导致非阿里出去的用户对很多概念难以理解,使用方式也不习惯。于是,在 2020 年的时候,云效又推出了一个全新的版本:云效 2020。在这一版本中,云效变成了松耦合的多个基础工具的形态,包括 Codeup、Flow、Packages、Projex 等,各个工具既可以独立使用,也可以组合和互相打通。

云效 2020 比较好地满足了广大小微团队的诉求。但是很多中大型的研发团队,会发现基于这套产品做研发协同,过于灵活了些,管不住是很多人面临的问题。

这时候,我们意识到对于中大型的研发团队,他们都有自己的研发规范,靠这种松耦合的基础工具组合是很难支撑研发规范的落地的。而回过头来看 Aone,作为一个深度定制化的产品,它很好地支撑了像前面提到的发布准入规范、安全规范等在阿里巴巴的落地,也成为了技术线对工程实践进行治理的主要入口,显然它解决了阿里巴巴自己对于研发规范的工具化落地诉求。

那么,我们能从 Aone 得到什么启发呢?

启发 1:应用作为研发工作的主入口

Aone 在项目之外,还有一个名为应用的研发工作的空间。项目和应用在设计初衷上有明显的区别,项目用于需求协作,更多地关注需求的流转和交付;应用用于开发、测试和部署发布,更多地关注工程交付。Aone 的应用,包含了跟这个应用相关的代码、配置、团队、环境、应用从开发到部署上线的流程、以及额外的管控策略如安全策略、发布策略等。

每个应用都有确定的归属团队,由这个团队负责其开发、测试、部署、运维直到生老病死。这种工作方式与当前很多敏捷型团队的实践如出一辙。应用的这种定位,使其非常适合承载研发规范。

启发 2:流水线挂在应用下,按环境排列

Aone 给我们的第二个启发,是流水线的管理方式。

流水线有两种常见的组织形式:一种是以 Jenkins 和云效 Flow 为代表,每条流水线都是独立的,通过建立分组和标签人为地进行分类;另一种是以 Gitlab 和 Travis 为代表,流水线是属于代码库的,每个代码库可定义自己的流水线。这两种方式,当团队上规模之后都有问题。第一种方式遇到的问题是流水线太多造成的管理难题;而第二种方式的主要问题是当代码库为大库,包含多个应用代码,或者从代码提交到部署要经过多个频率不同的阶段时,流水线与代码库之间很难一一对应,常常需要做很多妥协。

Aone 采用了另一种流水线组织方式:按应用组织流水线。这种方式并不只是简单将多条流水线按应用分组到一起,而是将流水线直接作为应用的一部分。应用中的流水线,其代码库是确定的,这就避免了流水线影响其它应用。同时,Aone 收口了应用的部署,且在流水线的创建上通过内置的模板做了全局管控,将发布准入等卡点内嵌到了流水线中,由大团队管理员统一管控,从而保证了发布的安全可控。像双十一的封网,某些核心应用的强制分批发布,就是在这一管控基础上实现的。

启发 3:分支模式内置在应用中

Aone 带来的第三个启发,在于分支模式的内嵌。

作为企业内部的研发工具平台,Aone 可以按照整体架构的要求,规范每个应用的分支模式,并在工具上,对其中某些分支模式做深度的集成。好的方面,任何一个开发者,进入到应用的开发中,他的工作方式都必须遵循这个应用的分支模式,否则流程上无法往下走;不好的方面,正由于分支模式的深度集成,导致支持更多的分支模式时,Aone 需要付出很多额外的成本。因此在面向更广泛的场景时,产品的设计者需要在深度集成与支持广度上进行取舍。

启发 4:以变更作为应用交付的主对象

Aone 带给我们的最后一个启发,是变更这个阿里特有的工程交付概念。

本质上,Aone 定义在应用内的变更,约等于应用的一个 feature。任何一次应用交付,都以创建变更作为起点,经过各个流水线阶段,直到生产发布完成后关闭。Aone 给我们展示了这一概念的具象化定义,以及实际应用的场景和优势。例如,某团队强制要求所有的应用开发都要关联需求或缺陷,因此,开发者拿到一个需求,进入开发的第一步,就是在应用上创建变更,并关联需求。

变更的主体是应用,这一设计让开发者更倾向于以单应用持续部署的思路去进行开发、测试和发布。更为重要的是,由于有变更以及变更和需求的关联关系,研发效能的度量有了客观和精细化的数据基础。

📝 小结: 让研发规范管得住是中大型研发团队的核心诉求。作为一个内部的研发工具平台,Aone 很好地支撑了阿里巴巴研发规范的落地,通过 Aone 这个例子,我们发现以下 4 点对于管住研发规范有很大的帮助:

  1. 统一应用研发和运维的入口为应用
  2. 按应用统一编排流水线,并通过流水线模板进行全局管控
  3. 应用内置分支模式
  4. 显式定义开发任务(Aone 称之为变更),并用其串联应用交付全过程

我们的解决方案及背后的设计

在问题的牵引和 Aone 的启发下,我们经过多轮迭代,最终形成了阿里云云效的一个新的子产品:云效应用交付平台 Appstack,并在该子产品中引入了 2 个新的概念:研发流程、变更。

接下来,我会分享下背后的一些设计思路。

寻找 DevOps 的模型

在早期对这个问题和解决方案进行跨角色讨论的时候,我们遇到了一个非常典型的问题:

由于研发规范涉及到的概念较多,沟通的时候,不同的角色很难在同一个维度讨论,导致沟通的效率非常低,以致于很难得到结论。

这是一个非常常见的(业务)需求分析问题,当业务、产品、技术等拥有不同背景的成员在一起讨论某个需求的时候,需要保证大家对需求有一致的理解,既不能太偏向技术术语,又不能与技术实现映射不起来。

我们解决这个问题的方法类似 DDD,各个角色一起面向 DevOps 的领域模型进行问题的讨论和场景的梳理。有兴趣的读者可以查看 BizDevOps 白皮书,里面对这个模型有系统的解释。

参考:必致(BizDevOps)白皮书2022-藏经阁-阿里云开发者社区 [ 1]

基于模型讨论最大的好处是所有参与方可以在相同的上下文语境下讨论,通过问题和研发场景牵引模型的演进,进而推动产品的迭代更新。

下图是研发流程相关模型的精简版。

图片

可以看到,我们继承了 Aone 的应用和变更的概念,但在应用的流水线之上加了一层研发流程,这几个对象构成了应用交付域的核心模型。

研发流程 = N * 流水线 + 管控

我们对研发流程的定义为:研发流程是多条流水线的顺序组合,每条流水线代表一个研发阶段,可独立运行,但又受到定义在研发流程上的管控策略的约束。其表现形式类似下图:

图片

在这张图中,一个研发流程按执行阶段划分为 n 条流水线,每条流水线都有在研发流程中定义的管控规则,比如测试准入规则。

管控规则包含哪些内容?为什么定义在流水线之外呢?

简单来说,管控规则包含的是对一系列键值对的检查,这些键值对来源于该条阶段流水线外部的执行记录,如另一条阶段流水线、外部安全工具等。比如,每一条阶段流水线执行完毕后,都会记录一个键值对,标识这次所验证的对象的执行结果是否成功。正因为管控规则的数据都来自于当前流水线之外,且作用于整条流水线,其配置就更适合放在流水线之外了。

基于这一设计,我们第二章的研发规范,可以表示为如下形式:

图片

变更背后的设计

细心的读者可能会发现,在上一节我们讨论研发流程的管控规则的时候,提到会将流水线执行记录标记到某个对象上,那么这个被标记的对象是什么呢?

这一块是我们在建设过程中讨论时间最长和争议最多的部分。从原始诉求的角度,我们需要一个对象来全流程跨阶段记录验证结果,作为管控规则的准入;从技术实现的角度,已有的对象(分支、代码 commit、流水线执行记录)都不合适,我们不得不创建一个新的对象来定义它。本质上,我们需要的是一个能聚合与某个交付有关的所有技术活动的对象,像极了平时常说的技术任务。

按照 BizDevOps 白皮书中的定义,我们认为:变更请求是为实现特定的产品需求或修复产品中的缺陷,某个应用所需要完成的工作。变更请求的视线,是包含设计、编码、提交和部署等在内的完整过程。与分散的技术任务不同,变更请求聚合了为响应特定产品需求,应用上所发生的所有技术活动。

变更背后是如何设计与实现的呢?

我们以上一节最后的示例为例,我们希望,当某个 feature 分支在开发阶段通过检测并合入到 develop 分支后,能够在集成阶段自动识别它是否通过了开发阶段的验证。因此,我们会在创建变更的时候,关联上明确的变更分支,作为变更的源头,之后在该分支上的所有提交和流水线执行,我们都会进行追踪,并关联到变更对象上。而管控规则,则是对记录到变更对象上的数据的消费。将研发流程与其变更背后的数据分开看,大致关系类似下图。

图片

我们用研发流程和变更解决了某个应用的研发规范问题,那如何将这一规范批量落地到所有应用呢?我们的答案是使用模板并收口权限。欢迎查看我们前面的文章:如何批量管理上百个微服务,上千条流水线

引入研发流程后带来的改变

现在我们通过两张图,看下引入应用研发流程后所带来的改变。

先看没有研发流程时的状态,见下图:

图片

此时,企业的研发资产和研发活动,会按类型天然揉在一起,比如代码都在代码库里,环境都在集群里,测试都在测试库里。稍大一些的团队,都拥有大量的资产,每天都会产生大量的活动,由于这些都是分散的,某一点的信息只在某些人的记忆里,还可能出现偏差。日常研发活动中,团队内和团队间需要大量线下的协同工作,来保证整体工作往前推进,而如果遇到架构升级、资源优化、安全治理等横向的工作,则需要由专人负责盘点和推进,往往旷日持久。另外,团队内和团队外,只能观察到最上层的需求交付数据(假设需求管理执行完善)和最下面的单点活动数据(如代码提交次数),这些数据无法建立起关系。在这种情况下,数字化的产研协同和效能改进无从谈起。

再看引入了研发流程时的状态。

图片

在这一状态中,无论是代码、配置还是其它资产,均可以归属到某个应用上, 多个应用又可以按照逻辑关系,组合为一个系统。更进一步,企业可以根据应用的所有者,做到按团队和人来管理资产,资产的盘点和治理将是顺理成章的事情。

另外,研发人员都以特性开始研发工作,每个特性的研发都需要遵守该应用的研发规范,且与该特性有关的所有研发活动都能被识别和管理起来。更进一步,由于研发活动都按照特性内生关联在一起了, 企业可以基于每个特性研发活动的数据,及时发现可能存在的风险,并持续对研发规范进行优化调整。

📝 小结:

模型应当是开放的,与产品无关的。

模型比产品更贴近 DevOps 本质,应通过模型的升级来牵引产品的演进。

研发流程本质上是包含跨阶段管控能力的多条阶段流水线的编排,通过在流水线外增加管控能力,让研发规范在单个应用可以被管得住。

我们假定每个研发活动都是有目的的,期望通过变更请求来描述这个目的,进而把聚合起来的研发活动,与需求协作连接起来。

我们期望通过使用模板和控制权限,达到在企业中批量管理研发规范的目的。

未来的研发规范会怎样

前面我们一起看了看脚下的大地,现在让我们仰望星空,思考未来的研发规范会有哪些变化?

在第一章我们提到,研发规范的出现,是为了通过规范团队的研发协同和工程实践,降低出现软件危机的风险。而软件危机的根源在于软件复杂度的提升及软件团队规模的变大。换言之,因为当前软件研发的主体是人,软件活动受限于人自身的脑力和体力,是一个极为耗时且容易出现偏差的活动。因此,如果软件研发的主体发生改变,软件活动的耗时大幅降低,软件团队的协作需求大量减少,研发规范必然会需要重新设计。

回顾研发规范出现后的几十年,我们在软件研发的工程效率上并没有本质的提升。但 AI 成为了最大的变量。随着 AI 的发展,研发活动的参与者和主体都可能发生变化。在此,我不负责任地 YY 一下:

  • 在 AI 时代,AI 工程师才是研发活动的主体,人将成为 copilot 和 observer。
  • 为 AI 工程师制定精细的研发规范,可能成为组建 AI 工程师团队的首要任务。
  • 对 AI 工程师来说,专业工作和角色协同不再占用大量时间,研发规范将会更为精细和标准,软件开发规范将会越来越靠近现在的工业生产规范。
  • 安全将变得至关重要,业界将会出现研发规范的底线安全标准。

最后,如果您对云效的研发流程感兴趣,希望免费试用,或者是研发规范落地上存在困难,希望与我们交流,欢迎加入下方钉钉群(群号:72815007140

相关链接:

[1] 必致(BizDevOps)白皮书2022-藏经阁-阿里云开发者社区

https://developer.aliyun.com/ebook/7847


阿里云云原生
1k 声望302 粉丝