写点什么

真正的敏捷工作流 —— GitHub flow

2019 年 9 月 20 日

真正的敏捷工作流 —— GitHub flow

7991 年,随着极限编程(Extreme programming)方法论的提出,持续集成(Continuous integration)也随之成为一项标准化的敏捷实践,被逐步应用于各类软件的开发流程中。


9102 年的今天,持续集成的概念已经在软件开发领域生根发芽,广泛应用于不同平台及设备的项目开发,极大提升了项目迭代速度,降低了维护成本。


不过,作为“敏捷”的固有属性,持续集成也并不仅限于特定的模式,不同的项目可能遵循不同的实践,形式多种多样,效果可能也参差不齐。


为了解决这些问题,一些 Workflow 的通用模式被提出,而本文的主角,就是其中的天之骄子 —— GitHub flow。


GitHub flow 是什么?

GitHub flow,顾名思义,就是 GitHub 所推崇的 Workflow。千万不要理解成 GitHub 上才能用的 Workflow


其官网的描述为:


GitHub flow is a lightweight, branch-based workflow that supports teams and projects where deployments are made regularly.


从中我们可以得出的信息是 —— 这段描述完全就是废话 GitHub flow 具有很高的通用性。


为了更便于了解 GitHub flow 的内容,我们从流程图入手:



其中的主要流程为:


  • 新建分支(Create a branch);

  • 提交修改(Add commits);

  • 创建 PR(Open a Pull Request);

  • 代码评审(Discuss and review your code);

  • 部署(Deploy);

  • 合并(Merge);


细心的同学可能很快会发现,GitHub flow 最大的亮点在于部署(Deploy)发生在 合并(Merge)之前,这就是 GitHub flow 的核心,非阻塞式集成 —— 在产生任何副作用之前得知当前修改的所有集成效果,达到真正的持续集成。


GitHub flow 有什么优势?

GitHub flow 的核心优势在于其流程带来的自动化可能性,能够做到其它流程无法实现的检查过程,并极大简化开发团队的体力劳动,真正发挥自身的价值。


主要体现在以下方面:


基于修改的检查

基于修改的检查(Change-based checking) 是相对于全局检查(Global checking)的概念,最典型的例子就是代码覆盖率。项目中一般会设立覆盖率的最低阈(yù)值,并在流水线中进行检查。


根据著名的覆盖率第一定律:


随着时间的推移,项目中的实际覆盖率必将会无限趋向于要求的覆盖率。—— 沃兹基硕德


如果项目中配置的最低要求是 90%(暂不考虑覆盖率类型),那么就不要指望实际覆盖率能够超过 95%。于是问题来了,全局覆盖率要求会导致什么样的严重后果呢?


我们考虑一个假象项目,总共有 100 行代码,覆盖率要求 90%,实际覆盖率 90%。有一天,项目组成员小明发现其中有 10 行无意义的 console.log(42),决定将其删除。


学过初等数学的我们都知道,对于 0~1 之间的分数,分子分母同时增加相同数值时,分数的值会增大;反之,分子分母同时减少相同数值*时,分数的值会减小。(这里要求结果仍然处于 0~1 之间。)


如果还不能反应过来的话,可能要考虑补充六个核桃了。删除无用代码的结果是,覆盖率不再满足要求,从而无法通过流水线。


90/100 * 100% = 90.00%80/90  * 100% = 88.89%
复制代码


之后,小明可以作出以下几种选择:


  1. 撤销之前的修改,保留无用代码;

  2. 降低全局覆盖率要求;

  3. 从其它覆盖率不足的地方补充代码覆盖;

  4. 找到之前导致覆盖率不足的人,要求其补充代码覆盖。


选项 1 固然是最简单的方案,直接当作无事发生。


选项 2 虽然也简单,但是既然当前覆盖率能够降到 90%,如果降低要求以后必然还会继续下降,同时如果被其他人发现可能遭到质疑(Challenge)。


选项 3 中一个覆盖率不足的问题可能继续分为两种子类型:案例遗漏非测试友好。前者是忽略了某种应当覆盖的情况,而后者是代码的设计本身导致无法合理测试。对于前者,如果缺乏上下文而直接把当前行为当作预期,可能会埋下错误隐患(如果未覆盖当前行为本身是未定义行为甚至错误行为);对于后者需要进行额外的重构,仍然具备前述问题(在测试覆盖不足的情况重构?),并且可能导致原问题递归(如果重构本身减少了代码量)。


选项 4 原则上是最正确的方案,但实际上可行性很低。如果小明找到小红,告诉她一年前编写的代码覆盖率不足,那么得到的回应多半是:我当年都跑得好好的,你一改动就挂了,不是你的问题是什么?


归根到底,不论考虑哪种选项,对于小明而言,学到的只有一件事:


永远不要做会减少代码的修改!


永远不要做会减少代码的修改!


永远不要做会减少代码的修改!


一旦开发团队中每个人都认识到这一点之后,之后的开发过程就会向着堆垃圾的方向发展:能不动原有代码就不动,实在不行写个 if插进去。提取公共代码?双倍的覆盖就是双倍的_快乐_安全,怎么能说不要就不要?


不过,一旦我们使用合并前集成(Integration before Merge)的方式,便能够得知每个改动中每个文件的覆盖率情况,从而在开发过程中主动避免覆盖率下滑,把质疑集中到问题的来源 —— 提交代码并且覆盖率不足的人身上。



非错误级反馈

非 GitHub flow* 的流水线中,永远只存在一种反馈方式 —— 报错。(为了保持简洁,这里将所有不符合 Integration before Merge 的流程统称为「非 GitHub flow」。)


这时候有人可能会说,我们可以向流水线的控制台输出里打印日志。不过我可以保证,没有人会在正常构建的情况下守着看完每一条日志,一个合理设计的流水线也不应该需要主动关注这里的内容导致不必要的效率浪费。


日志的内容往往绝大部分都是非关键信息:



即便快速浏览日志,恐怕也很难发现关键信息。


不过,项目开发中往往存在很多非关键因素,平常不会太过关心,但一旦问题严重之后又会很麻烦。一个很好的例子就是应用体积。假如开发过程中对体积毫不关心(内网可能传输很快),那么等到用户真正无法容忍加载时间而导致使用率急剧下降的时候,还得专门回过头来做体积优化*。(如果本身就是打算靠创造额外优化工作赚钱的话,可以当我没说。)



通过 GitHub flow,我们能够在合并之前得到所有相关的信息,并自行判断问题的严重性(其他 Reviewer 也有义务判断)。如果本次改动并没有添加新的依赖,但是构建后大小急剧增加,那么可能就需要检查文件引用或者构建过程存在问题。


由于是基于集成结果的信息提示,因此还可以设置出现条件,例如某文件体积变化超过 0.5%。这样能够避免被固定消息所打扰,只关心必要内容。


除了自动执行的被动检查项目外,对于需要可观成本的检查,往往设计成主动检查项目。一般通过 PR 的标签或者评论内容进行触发,类似于:



性能测试(Performance testing) 就是一个较为典型的例子,如果小明不畏艰难险阻对实现代码进行了深度重构,那么在合并前就必然选择进行性能测试来避免非预期的影响。同理,如果只是添加了测试代码,那么性能测试将完全没有必要。


同样的,Reviewer 也应当评估是否所需的主动检查项目都被执行。


无限环境

多团队协作项目中,一个常见的痛点就是需要根据自身或者外部的需求准备各种环境,然而一些环境在大部分时候都不会使用到,往往需要在不明所以的情况下突然增加或者调整环境配置。


这时就可以回归到 GitHub flow 的重中之重 —— 合并前部署。


所谓的无限环境,就是自动将当前 PR 中的最新提交*部署到一个临时环境中,并返回该环境的 URL 地址。(如果资源丰富的话也可能部署每一个提交以方便比对。)



为此,环境准备工作将变得非常简单,只需要修改相应配置文件并创建 PR,即可得到一个对应的新环境。这一切甚至不需要依赖本地开发环境,而是直接在代码平台的在线编辑器中完成。


由于可以直接预览当前修改,不会再出现不必要的疑 车 虫无据的情况,Reviewer 有任何怀疑时便可以直接在预览环境中验证,而非凭空猜疑。Reviewer 也可以放心大胆的验证自己的怀疑,不需要在本地开发环境耗时耗力地切换。


有条件时甚至可以为预览模式设定特殊构建模式,例如高亮效果用于定位修改内容:



这样可以极大提升 Review 效率,降低 Reviewer 的负担。


自动化工作流

项目开发中往往有大量的时间被浪费在等待。等待构建、等待测试、等待 Review ……而这一切,都可以依靠 GitHub flow 来进行改善。


由于 PR 中能够运行所有必要的检查,所以本地开发环境中仅仅需要关注最可能受到影响的内容(例如当前文件的测试),而把其它不在固定影响范围的检查都转交给 PR。由于 PR 的工作机制,即便存在冲突无法合并也不会导致 Push 失败,并且 Push 本地代码后便可以立刻关电脑走人,即便 PR 检查失败也不会有任何后果。


PR 中能够利用 CI 环境,不受本机执行能力限制,因此可以并行检查所有需验证项目:



这里的检查本质上仍然是 Code Review,只不过参与者不是自然人。


检查期间,开发人员可以充分利用碎片时间处理其它事务,而无需关心检查进度。如果任何项目检查失败,将立刻收到邮件通知。


如果所有检查项目均已通过并且当前 PR 并非 Draft 状态*,能够自动通知 Reviewer 进行代码 Review,并且在所有 Reviewer 同意后自动 Merge,在 Happy Path 下完全无需再次人工干预。


(Draft PR 是 GitHub 最近推出的功能,用于标记当前 PR 为未完成状态。其它平台可能将采用不同的判断方式。)


说到 Reviewer,就不得不提 Code Owners。Code Owners 是 GitHub 的内置功能*,能够配置每个文件/文件夹的所有者,在 PR 完成时根据修改文件的范围自动向添加相应文件所有者为 Reviewer,只有当各个 Group 的 Reviewer 都同意时才允许合并。(一些第三方工具也提供了类似的机制,功能可能更加强大。)



Code Owners 充分保障了项目的可维护性,每个 Code Owner 同时具备以下职责:


  • Domain export:对相关代码有深度的了解,知晓其历史背景与特殊行为,能够快速发现隐藏问题;

  • Coordinator:掌握每次修改的内容及原因,避免代码/环境冲突及已有行为被破坏;

  • Contact:如果对相关代码有疑问,能够立即确定联系对象而无需层层转发;

  • Responsible person:如果相关代码出现了什么意外,负责背锅,避免不必要的推诿。


例如将环境配置文件分配个某个/某些项目组成员,那么他们就能够充分知晓各个环境的使用情况,作出合理安排。


如何开始使用 GitHub flow?

使用 GitHub flow 的基本要求有:


  • 具备一个代码版本控制环境;

  • 具备一个持续集成环境;

  • (可选)具备 CI 环境的管理员权限;

  • 能够创建一个有权限访问 VCS 平台的机器人帐号;

  • 能够自由使用 VCS 平台的 WebHook API;

  • 能够自由使用 CI 平台的 Trigger API;

  • (可选)能够自由使用 CI 平台的状态查询 API;

  • 能够创建一个高可用的内部服务器用于机器人帐号的运行;

  • 能够决定开发团队的工作流程;

  • 能够投入成本改善基础设施;


遗憾的是,我至今没有过这种条件,如果你有能力去实践 GitHub flow,希望能够珍惜这次改善开发体验的机会,让更多人了解这种流程优化带来的巨大效率优势。


如果有任何具体的技术问题,也欢迎进一步的讨论。


写在最后

以我个人的体验,GitHub flow 是 世界上唯一的真理 真正能够拯救开发效率的敏捷实践,将开发人员真正从体力劳动中解放出来,从而能够专注于学习与思考。


如果你也觉得 GitHub flow 真正拯救了你的项目开发,不妨将它继续推广下去。


作者介绍


余泽江,ThoughtWorks 咨询师,拥有多年 Web 应用开发经验,主要专注于前端框架底层实现与 Web 规范演进。


本文转载自 ThoughtWorks 洞见


原文链接


https://insights.thoughtworks.cn/real-agile-workflow-github-flow/


2019 年 9 月 20 日 08:002229
用户头像

发布了 65 篇内容, 共 12.6 次阅读, 收获喜欢 158 次。

关注

评论 1 条评论

发布
用户头像
进来就是7991年......
2019 年 09 月 20 日 11:10
回复
没有更多了
发现更多内容

week11-conclusion

J

前端开发:Node版本引起的报错问题

三掌柜

vue.js 前端

产品经理训练营笔记-业务流程与产品文档(一)

.nil?

产品经理训练营

Android 完全符合规则但很头疼的Json映射成一个树结构且可折叠的列表?

第三女神程忆难

Java android kotlin 安卓

Elasticsearch 分页搜索以及 deep paging 性能问题

escray

elastic 七日更 死磕Elasticsearch 60天通过Elastic认证考试 2月春节不断更

《零基础看得懂的 Python 入门教程 》——(二)使用武器增加攻击力

1_bit

Python 编程语言 编程教程

2. 无门槛学会数据类型与输入、输出函数,滚雪球学 Python

梦想橡皮擦

Python python 爬虫 2月春节不断更 python入门

Kubernetes 原生 CI/CD 构建框架 Tekton 详解

火山引擎

字节跳动 Kubernetes 云原生 Tekton CI/CD

最好的IDEA debug长文?看完我佛了

YourBatman

eclipse debug IntelliJ IDEA 远程调试

从0到1实现一个简单计算器

codevald

Java 项目 计算器 动手实践

机器学习笔记之:

Nydia

大作业二-请用思维导图画出架构师训练营所有技术知识点

未来已来

VoltDB让Kafka支持复杂数据流驱动的实时业务决策

VoltDB

数据库 kafka 分布式系统 VoltDB

从云数据迁移服务看MySQL大表抽取模式

华为云开发者社区

MySQL JVM JDBC 数据迁移

无意间发现 Google 代码模板,分享给大家!

C语言与CPP编程

c++ JavaScript objective-c 代码规范 Python 编码格式

对话京东科技算法科学家吴友政:回望2020,NLP技术发展速度强劲

京东科技开发者

人工智能 自然语言处理

Spring Boot 微服务性能下降九成!使用 Arthas 定位根因

阿里巴巴云原生

Java 微服务 云原生 中间件 Arthas

架构师训练营第六周作业

跳蚤

面试官:高并发下HashMap的死循环是怎么形成的?

Crud的程序员

Java hashmap

一文总结GaussDB通信原理知识

华为云开发者社区

数据库 通信 框架 GaussDB 计算

架构师训练营-架构大作业(一)

花果山

架构师训练营第2期

week11-homework

J

前端必学必会-多媒体-本地存储-浏览器与服务器的交互-通信功能

魔王哪吒

学习 程序员 面试 前端 2月春节不断更

架构师训练营第2期 大作业 (一)

月下独酌

架构师训练营第2期

Ebean ORM框架介绍-1.增强注解

Barry的异想世界

Spring Boot jpa ORM Ebean

中国移动工程师浅析:KubeEdge在国家工业互联网大数据中心的架构设计与应用

华为云开发者社区

大数据 数据采集 工业智能体 边缘数据中心管理 EDCM

缓存设计的好,服务基本不会倒

Kevin Wan

go 缓存 微服务 微服务架构 microservice

Arthas 使用的各类方式

阿里巴巴云原生

Java 微服务 云原生 中间件 Arthas

Serverless 场景下 Pod 创建效率优化

阿里巴巴云原生

Docker Serverless 容器 云原生 k8s

几幅图拿下 ARP 协议

飞天小牛肉

Java 程序员 计算机网络 网络协议 2月春节不断更

逼疯UE设计师,不可不知的提升产品用户体验的10个测试方法

华为云开发者社区

产品 测试 UI 用户体验

真正的敏捷工作流 —— GitHub flow-InfoQ