八、管理源代码分支的模式(一)

一、基本模式

1.1 Source Branching

Create a copy and record all changes to that copy.
源代码管理系统记录每一次提交的每个改变,让代码合并变得简单,但不能让代码冲突消失。如果两个人同时修改同一个文件中的同一个变量定义,并且修改的值不一样,那么源代码管理系统在没有人为干预的情况下就无法解决这个冲突。让它更尴尬的是,这种文本冲突至少是源代码控制系统可以发现的,并提醒人们去查看,还有一些冲突是合并时发现不了,但是系统就是不工作。比如,一个人改变了函数名称,但另一个人还是引用的原来的函数 名称,这就会导致语义冲突。当这种情况出现时,系统可能会编译失败,也可能编译成功但在运行时失败。

image.png

大部分人会像上面那张一样画分支图,实际应该时下面这张图,随着时间的推移变化会越来越大。
任何从事并发和分布式计算工作的人都对这种问题很熟悉。开发者并行更新同一个状态。我们需要组合这些更改并序列化成一致的状态。事实上,让系统执行并运行正确已经越来越复杂了。这意味着对于共享的状态有复杂的验证标准。不可能创造一个确定的算法来达到一致。需要人为的达到一致,这个一致也许需要混合不同的更新。

1.2 Mainline

A single, shared, branch that acts as the current state of the product
mainline是一个特殊的分支,代表的是团队代码的当前状态。无论什么时候我想开始新的任务,我就会将mainline克隆到本地开始工作。无论何时我想和其他团队分享我的工作,我就会更新mainline*,理想状态是使用Mainline Integration 模式,稍后会讲到。

不同的团队对这个特别的分支取不同的名字。常常会鼓励使用这个源代码系统默认的。比如在git通常叫“master”,svn通常叫“trunk”。

必须强调的是mainline是一个单独的、共享的分支。当人们在git中谈论“master”时,他们可能表示的不同的东西,这是由于每个仓库克隆有自动本地master。通常团队有一个中心仓库---一个共享的仓库作为项目的单点记录,也是所有克隆的原始副本。开始一项新的工作通常就是克隆中心仓库。如果已经克隆过,当我开始工作时,我会先从中心仓库拉取一份最新的。这种情况下,在中心仓库中mainline就是master分支。

当我开发一个新功能时,我会有一个自己的开发分支,这个分支就是我的本地master,或者我会创建一个单独的本地分支。如果我在这个分支上开发了一段时间,我会不停的从mainline上拉取新的变更,然后合并到我的单独的分支上。

相同的,如果我想为产品创建一个新的发布版本,我可以从当前的mainline开始。如果我想修复bug,可以使用Release Branch

When to use it

还记得在2000年的时候,和一个工程师聊天,他的工作是合并团队开发的代码。每次他会给团队的每个成员发送邮件,然后成员会发送自己的代码文件给他。他然后拷贝这些文件和自己的代码集成,然后编译代码库。这通常会花费他几周的时间。

相反的,通过mainline,任何人可以很快的使用最新的代码开始工作。更进一步,mainline不仅让当前代码库的状态更易看见,也是我要讲的其他模式的基础。

1.3 Healthy Branch

On each commit, perform automated checks, usually building and running tests, to ensure there are no defects on the branch

由于Mainline是共享的、经过验证的状态,所以保持它在一个稳定的状态很重要。

为了做到这一点,我们需要保证编译都成功并且代码没有或者很少有bug出现。有以下经验可以借鉴:

通常测试要花费大量的时间,所以可以使用 Deployment Pipeline将测试分成几个阶段。第一个阶段需要尽可能地快,通常不长于10分钟,但仍要有测试力度。这一阶段被称为“commit suite”,也叫做“单元测试(the unit test)”。

理想状态下每一个提交都应该触发一次编译。然后如果测试很慢,比如是性能测试,那么这样就不实用。

代码运行没有bug并不意味着就是好代码。为了保证交付地稳定性,我们需要保证代码地内部质量。一个常用地方法是 Reviewed Commits

When to use it

每个团队应该在他们的开发流程中具有清晰的标准来保证分支健康,这具有巨大的价值。如果mainline健康,一个开发者可以随时开始工作只要拉取最新的分支即可。如果mainline不稳定,需要花费大量的时间来修复分支问题。

也要保证本地分支代码健康,这样就可以很容易合并到主干分支。

二、Integration Patterns

2.1 Mainline Integration

Developers integrate their work by pulling from mainline, merging, and - if healthy - pushing back into mainline
mainline表示了团队软件的当前状态。有mainline的一个好处就是简化了集成。每个开发可以在自己本地分支上做集成。

下面举一个例子来说明,比如有个开发者叫小s,她要开发一个新的功能,用git将主干分支克隆到自己的本地仓库:


image.png

当她在工作的时候,她的同事小V推送了一些变更到mainline。由于小V在自己的分支上开发,所以她并不知道mainline上的变动:

image.png

不久,她想开始集成了。首先她要拉取mainline当前的代码到本地,这会拉取到小V的变更。由于她在本地仓库上工作,提交将在origin/master上显示为一条单独的代码线。

image.png

如果小S幸运,合并小V的代码没有冲突,否则就会有冲突需要解决。如果是文本上的冲突,源码控制系统可以解决,但如果是语义上的冲突就很难解决。这时, Self Testing Code就非常有帮助。由于解决冲突会花费大量的时间,并且对代码质量也会产生风险,所以我将他们标记成黄色

image.png

这时,小S需要验证她合并的代码满足mainline的健康标准。这通常意味着编译代码并运行所需要的测试。即使是没有冲突的合并,也要进行编译测试。任何提交中的错误都可能是由于合并导致的。知道这个可以帮忙她定位问题,至少首先应该从合并代码中找到线索。

通过这种编译和测试,她成功地将mainline的代码合并到自己的代码库,但是她还没完成和mainline的集成。为了完成集成,她必须推送她的变更到mainline。集成包括拉取和推送。只有她完成推送后,她的工作才能开始和其他项目集成。

image.png

2.2 Feature Branching

Put all work for a feature on its own branch, integrate into mainline when the feature is complete.

使用功能分支,开发者在开发一个功能特性时打开分支,然后持续在这个分支上工作直到完成,最后集成到mainline。

例如,还是以小S为例,她开发一个新功能:添加本地的销售费率到他们的网站。她以当前产品的稳定版本开始,拉取mainline到自己的本地,然后以当前mainline为基础创建一个新的分支。她以后一直在这个分支上开发,提交了很多代码。


image.png

She might push that branch to the project repo so that others may look at her changes.
她也可以推送这个分支到远程仓库,这样别的开发者也可以看到她的变更。
当她工作的时候,其他提交会在mainline。所以时不时的,她会从mainline拉取新的代码,这样就可以知道有哪些改变会影响她的功能。


image.png

需要注意到这并不是我上面描述的集成,是因为她并没有推送回mainline。这时她只能看到她自己的工作,其他人的看不见。

一些团队喜欢让所有的代码都保存在远程仓库。这时候小S就必须推送她的分支到远程仓库。这也允许团队中的其他成员可以看到她当前的工作状态,即使还没和其他人的代码集成。

当她完成自己的功能开发时,她会执行 Mainline Integration 来合并这个功能到产品中。

image.png

如果小S在开发多个功能特性,那么她可以为每个功能创建独立的分支。

When to use it

Feature Branching在当今的工业生产中是一种很受欢迎的模式。为了谈论何时用它,我需要介绍它的主要的替代方法- Continuous Integration。但是首先需要谈论频繁集成的作用。

2.2.1 Integration Frequency

集成的频繁程度对一个团队运作有很大的影响。来自State Of Dev Ops Report 的调查研究表明精英开发团队比低绩效开发团队更频繁的集成。这个调查符合我和大多数同行的经验预期。

Low-Frequency Integration

以low-frequency 案例为例,开始讲述。还是以小S和小V为例,她们各自开始自己的工作,拉取mainline到自己的分支,然后做了一些变更但还未提交


image.png

他们工作时,其他人提交了一个变更到mainline:


image.png

这个团队通过保证分支健康来工作,每次提交都会拉取mainline。小S前两次提交没有拉取mainline是由于mainline未发生变更,但是现在她需要拉取M1:


image.png

我将这次merge用黄色方框表示。这次merge提交S1..3到M1。不久小V也需要做同样的事情:


image.png

这时两位开发者都和mainline保持同步,但是她们之间还未集成,因为代码都是隔离的。小S无法察觉到小V的V1..3做的变更。

小S又做了两次提交,然后准备向mainline集成,这次合并很容易,因为她已经拉取了M1的变更。

image.png

然后,小V就会有一个更复杂的体验。当她集成到mainline时,她不得不集成S1..5到V1..6。


image.png
High-Frequency Integration

在前面的例子中,两位开发者做了很多次提交后才开始集成。让我偶们看看如果每次提交就和mainline集成会发生什么。

小V第一次提交后就和mainline集成,此时集成很容易。


image.png

小S第一次提交后也和mainline集成,但因为小V已经提交了,所以她需要做一次merge,但由于只需要merge V1和S1,这次合并的代码量很少。

image.png

小S的下一次集成只需要推送代码就行了,这意味着小V的下次提交要合并小S的最近两次提交。然后,这仍然是一个相对较小的merge。


image.png

当有其他人推送代码到mainline时,小S和小V只需要按照以往的节奏合并代码即可。


image.png

和之前一样,这次小S只需要集成S3和M1,因为S1和S2已经集成过。这意味着小G在推送M1的时候不得不集成S1..2,V1..2。

开发者继续剩下的工作,每次提交都集成:


image.png
Comparing integration frequencies

让我们来看看上面两种方式的区别


image.png

这里有两个明显的不同。第一就如high-frequency integration名字所表示的那样,它比 low-frequency integration多很多次,而且更重要的是每次合并的代码量很少。越小的集成意味着越少的工作量,因为冲突越少。比工作量少还要重要的是,风险也少了。大合并的问题不在于工作本身,而在于工作本身的不确定性。

大多数时候,即使是大型合并也会进展顺利,但偶尔也会进展得非常非常糟糕。偶尔的疼痛会比一般的疼痛更严重。如果我比较花费额外的10分钟每个集成与1 / 50的机会花费6小时修复一个集成-我更喜欢哪一个。如果我只看努力程度,那么1 / 50更好,因为是6小时而不是8小时20分钟。但这种不确定性让50%的人感觉更糟,这种不确定性导致了对融合的恐惧。

我们从另一个角度来看一看两者之间的差别。如果小S和小V在第一次提交时就出现冲突会发生什么。在low-frequency场景下,他们直到小V的最后一次merge才会发现这个冲突。而在high-frequency 场景下,在小S第一次提交时就会发现。


image.png
image.png

频繁的集成增加了合并的频率但也减少了复杂性和风险。频繁的集成还能更快地提醒团队发生冲突。这两者是有联系的。令人讨厌的合并通常是团队工作中潜在的冲突的结果,只有在集成发生时才会浮出水面。

很多人没有意识到的是源码控制系统是一个交流工具。它可以让一个开发者知道其他人正在做的事情。通过频繁的集成,不仅可以立即知道代码有冲突,也可以知道每一个人的最新开发进展,以及代码库是如何演进的。我们不是孤身一人而是作为一个团队在一起工作。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,252评论 6 516
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,886评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,814评论 0 361
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,869评论 1 299
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,888评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,475评论 1 312
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,010评论 3 422
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,924评论 0 277
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,469评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,552评论 3 342
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,680评论 1 353
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,362评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,037评论 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,519评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,621评论 1 274
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,099评论 3 378
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,691评论 2 361

推荐阅读更多精彩内容

  • 现代的源代码控制系统提供了强大的工具,可以非常轻松的在源代码上创建分支。但最终分支还是要合并在一起,许多团队不得不...
    暴走的初号机阅读 1,310评论 0 0
  • 原文:https://martinfowler.com/articles/branching-patterns.h...
    林万程阅读 365评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,711评论 18 139
  • 在软件工程中,集成是一个漫长而又不可预测的过程。因此一些公司开始尝试让团队中的成员每天集成一次,这样开发者不会偏离...
    bylaw阅读 799评论 0 0
  • 简介 现代软件开发过程中要实现高效的团队协作,需要使用代码分支管理工具实现代码的共享、追溯、回滚及维护等功能。目前...
    Lucas66阅读 8,213评论 0 5