最近笔者所在公司发生了一起小风波,事情大概是这样的:市场部老大在给客户现场演示系统时,正讨论着一个主题,恰巧系统在切换到相关功能时出现了异常,导致功能不可用,现场有点尴尬。
显然,问题归咎于研发部。严肃的气氛下,我下意识在想自己是不是凉了,于是我迅速定位原因,发现是后端接口发生变更而未通知前端,责任人正好是刚来没多久的后端新人。虽然这次事故不是前端的责任,但让我发现了后端Team存在的问题,在版本控制上有较大的隐患,代码未经Review就入库发版了,这本质上是分支管理不合理导致的。
研发部门是一个整体,当着客户的面出了生产事故,这让大家面子上都不好看,所以我自告奋勇提出在研发部内部做一次Git分支管理的分享,看看能不能帮大家解决这个问题。我入职以来一直比较注意版本控制这块,但也是今年才比较系统地梳理研发流程和版本控制(去年是快速出产品的一年,管理上稍微糙一点),几个月前还特意总结了一篇《前端小微团队的Gitlab实践》,经过数月的不断实践和改进,我感觉这套Git体系基本覆盖了我司的研发流程,至今没出过事故,发版节奏一直良好。
其实几个月前我就想在部门内分享下我这套版本控制流程,但是一方面是考虑到自己刚摸索出来,不太熟练,另一方面是自己资历尚浅,如果跨Team直接给后端老哥们“上课”也不太好吧(其实这个顾虑是多余的~_~)。
嗯,大概是这么一个心路历程,而现在正是必须站出来的时候,我希望这次我的分享能为团队尽绵薄之力!具体分享的内容是这样的,且听我慢慢道来!
个人感受
Git对我们来说既熟悉又陌生。感觉熟悉是因为我们似乎已经掌握了大量常用的Git命令,感到陌生是因为我们在实际项目中总是用不好它。是的,我也有过这样的感受,直到现在,我觉得Git仍有很多待探索的空间,比如难以理解的git rebase,又或者是Git提供的Hooks,让自动化部署有了更多可能。甚至一些平台将代码托管,敏捷开发,CI/CD,DevOps融合到了一起,提供了一站式解决方案。
始于Git,却不止于Git,Git还有太多值得我们折腾的小惊喜。那么,今天我以如何在实际项目中运用Git分支管理这个主题作为切入点做一次内部分享。
分布式版本控制
我们知道,Git是一个开源的分布式版本控制系统,这让团队协作成为了可能。我们可以通过fetch/pull将远程仓库的代码拉取到本地,也可以将本地代码push到远程仓库。
而我们向版本库提交代码的一个基本方向是:
工作区 --> 暂存区 --> 版本库
- 当对工作区修改(或新增)的文件执行git add命令时,暂存区的目录树被更新。
- 当执行git commit命令进行提交操作时,暂存区的目录树写到版本库中。
分支管理
Git最核心的内容当然是分支管理,设置合理的分支可以让研发流程有条不紊。使用分支意味着你可以从开发主线上抽离出来,不影响主线的前提下进行工作,最后完成工作再通过git merge
将代码合入到主干分支上。
简单的分支管理
在生产实践中,一般来说,我们会保持至少三个分支,分别是开发分支develop,测试分支release,生产主干分支master。不同的团队或个人在分支命名上可能会有所差异,但是基本逻辑都是大体一致的。
- 开发分支
develop
:最不稳定的分支,所有和特性,缺陷相关的代码都会陆续地被提交到这个分支。 - 测试分支
release
:一个敏捷迭代结束时,正常情况下,所有develop
分支的代码都会被merge
到release
分支,准备发测试版本。 - 生产分支
master
:最稳定的分支,待交付的版本上线前,测试通过的release
分支会被merge
到master
分支。
然而很多团队在管理develop分支时存在一个很大的问题:所有开发者都直接向develop分支push代码。
这样会造成很多隐患,包括但不限于:
- 团队成员间代码冲突。当然,直接向
develop
分支push
代码也不是造成冲突的根本原因。但是,这会让冲突更容易发生! - 代码质量不可控。这个问题大家都比较清楚了,这是因为所有代码都没有经过Review就入库了!
- 版本不可控。相信大家都遇到过,临到上线时间点,突然发现某某开发者的转测功能存在重大缺陷,不能上线。这个时候,选出能上线的代码让人头疼!根本原因是开发者的代码都直接进了
develop
分支,这让挑选代码变成了一件非常复杂的事情!
可控的分支管理
那么如何才能解决上述痛点呢?我们可以从分支的设计上入手。
- 保护分支(Protected Branchs)。禁止开发者直接向保护分支提交代码,
develop
,release
,master
都应该被设置为保护分支! - 增加特性/缺陷分支,避免直接向
develop
分支push
代码。 - 增加代码Review环节,基本上所有代码托管平台都支持这个环节!
具体操作流程是这样的:
- 如上图所示,我们约定一个特性或一个缺陷就是一个开发任务,所有的开发任务都应该在本地建立独立的分支。
- 开发者在特性/缺陷分支上进行开发。由于我们禁止了向保护分支直接
push
代码,所以开发者完成代码编写后,需要将本地分支同步到远程同名分支。 - 在代码托管平台如Gitlab上发起Merge Request,请求将特性/缺陷分支合入到
develop
分支。 - Maintainer(一般是团队资深成员,拥有同意MR的权限)负责Code Review,确认基本无误后同意MR,代码就顺利进入
develop
分支了。 - 后面全量发版本的流程就简单了,无脑
merge
即可! - 如果不能全量发版,必须进行代码挑选,此时就需要
cherry-pick
出场了!
特别注意,一定要保证分支的原子性,一个分支只干一件事。千万不要写着写着代码,突然萌生了在当前分支顺手改另一个问题的想法,这可能会让你陷入更大的麻烦!
分支命名
取名字永远是个难题,组件如何命名,方法如何命名,这些问题在平时开发过程中总是让人抓耳挠腮。当然,Git分支命名也不例外。
我之前也试过分支语义化命名,但是也发现了要用有限的单词描绘出复杂的含义永远是个伪命题。如上图所示,我们可能会在做一个新功能时,把相关分支命名为feature/xxx
,而后面有优化类需求时,又会新建一个feature/xxx-optimization
之类的分支。然而,往往一个功能会有一次又一次的优化、变更或bug,采取这样的命名策略永远会让自己直面灵魂拷问!
并且在追溯问题时,这种分支命名方式往往让人心力交瘁!
那么如何命名能解决这样的问题呢?我采用了下面这种策略!
我在观察很多开源软件时发现,他们的维护者都会用issue来记录各种开发相关的活动。比如需求,缺陷都会被记录在issue中,这让我觉得用issue来管理分支也是一个非常棒的idea!
我们可以在创建issue时填写标题和描述,并且可以通过链接等形式与敏捷管理平台的需求和缺陷关联上,还可以给issue打上不同的标签,看起来会非常直观。
issue还可以与milestone(里程碑)关联,用于检验和衡量阶段性的成果!想要知道更多细节,不妨打开《前端小微团队的Gitlab实践》细致阅读!
而issue本身有一个编号,或者叫ID,这种唯一标识让我们命名分支变得简单。假定一个issue的编号是1,那么我们在本地创建分支时,只需要将分支命名为issue/1
即可,根据这个编号,我就能查到这个分支处理的是哪个issue,而打开Gitlab的issue,我就能知道这个issue与什么需求或缺陷有关。这不仅给开发者带来了方便,也让管理者变得更轻松!
实际项目中如何操作?
对上文中的知识有了一定了解后,接下来就是看看如何在项目中把这些知识运用起来,形成一个合理,高效的流程!我以新需求为例,简单画了一下流程,请看下图:
打通了这么一个主流程后,相信无论是修复bug,还是其他的场景,你都能举一反三!
分支节点可拓展
实际上,不同公司在分支节点上的数量是不一样的。有的公司可能从开发到上线,会涉及多套环境验证,这样下来,就可能对应多个Git分支节点。加节点也不用怕,结合git merge
和git cherry-pick
,理论上再多节点也能应付得过来!
所以,我也在内部分享结尾时,提出了增加预发布环境的建议。测试环境尽可能发挥想象,可以测试各种极端情况。而预发布环境尽量模拟生产环境,保证数据和流程的合理性。这样一来,结合测试环境和预发布环境,我们能覆盖更多的测试用例,上线故障率会更低!
VSCODE必备扩展:GitLens
最后推荐大家安装一个非常好用的VSCODE扩展:GitLens
有了它,我们就可以随时看到每一行代码最近一次的改动都是谁提交的。
这也避免了大家查问题时,突然翻到一行可疑代码,然后感叹:这是哪个傻X写的!
最后一查记录发现是自己写的......
科科,GitLens它不香吗?