一飘蚯、基本模式
1.1 Source Branching
Create a copy and record all changes to that copy.
源代碼管理系統(tǒng)記錄每一次提交的每個改變馍迄,讓代碼合并變得簡單,但不能讓代碼沖突消失局骤。如果兩個人同時修改同一個文件中的同一個變量定義攀圈,并且修改的值不一樣,那么源代碼管理系統(tǒng)在沒有人為干預(yù)的情況下就無法解決這個沖突峦甩。讓它更尷尬的是赘来,這種文本沖突至少是源代碼控制系統(tǒng)可以發(fā)現(xiàn)的,并提醒人們?nèi)ゲ榭囱ǖ辏€有一些沖突是合并時發(fā)現(xiàn)不了撕捍,但是系統(tǒng)就是不工作拿穴。比如泣洞,一個人改變了函數(shù)名稱,但另一個人還是引用的原來的函數(shù) 名稱默色,這就會導致語義沖突球凰。當這種情況出現(xiàn)時,系統(tǒng)可能會編譯失敗腿宰,也可能編譯成功但在運行時失敗呕诉。
大部分人會像上面那張一樣畫分支圖吃度,實際應(yīng)該時下面這張圖甩挫,隨著時間的推移變化會越來越大。
任何從事并發(fā)和分布式計算工作的人都對這種問題很熟悉间护。開發(fā)者并行更新同一個狀態(tài)亦渗。我們需要組合這些更改并序列化成一致的狀態(tài)。事實上汁尺,讓系統(tǒng)執(zhí)行并運行正確已經(jīng)越來越復(fù)雜了法精。這意味著對于共享的狀態(tài)有復(fù)雜的驗證標準。不可能創(chuàng)造一個確定的算法來達到一致痴突。需要人為的達到一致搂蜓,這個一致也許需要混合不同的更新。
1.2 Mainline
A single, shared, branch that acts as the current state of the product
mainline是一個特殊的分支辽装,代表的是團隊代碼的當前狀態(tài)帮碰。無論什么時候我想開始新的任務(wù),我就會將mainline克隆到本地開始工作如迟。無論何時我想和其他團隊分享我的工作收毫,我就會更新mainline*攻走,理想狀態(tài)是使用Mainline Integration 模式,稍后會講到此再。
不同的團隊對這個特別的分支取不同的名字昔搂。常常會鼓勵使用這個源代碼系統(tǒng)默認的。比如在git通常叫“master”输拇,svn通常叫“trunk”摘符。
必須強調(diào)的是mainline是一個單獨的、共享的分支策吠。當人們在git中談?wù)摗癿aster”時逛裤,他們可能表示的不同的東西,這是由于每個倉庫克隆有自動本地master猴抹。通常團隊有一個中心倉庫---一個共享的倉庫作為項目的單點記錄带族,也是所有克隆的原始副本。開始一項新的工作通常就是克隆中心倉庫蟀给。如果已經(jīng)克隆過蝙砌,當我開始工作時,我會先從中心倉庫拉取一份最新的跋理。這種情況下择克,在中心倉庫中mainline就是master分支。
當我開發(fā)一個新功能時前普,我會有一個自己的開發(fā)分支肚邢,這個分支就是我的本地master,或者我會創(chuàng)建一個單獨的本地分支拭卿。如果我在這個分支上開發(fā)了一段時間骡湖,我會不停的從mainline上拉取新的變更,然后合并到我的單獨的分支上记劈。
相同的勺鸦,如果我想為產(chǎn)品創(chuàng)建一個新的發(fā)布版本,我可以從當前的mainline開始目木。如果我想修復(fù)bug换途,可以使用Release Branch。
When to use it
還記得在2000年的時候刽射,和一個工程師聊天军拟,他的工作是合并團隊開發(fā)的代碼。每次他會給團隊的每個成員發(fā)送郵件誓禁,然后成員會發(fā)送自己的代碼文件給他懈息。他然后拷貝這些文件和自己的代碼集成,然后編譯代碼庫摹恰。這通常會花費他幾周的時間辫继。
相反的怒见,通過mainline,任何人可以很快的使用最新的代碼開始工作姑宽。更進一步遣耍,mainline不僅讓當前代碼庫的狀態(tài)更易看見,也是我要講的其他模式的基礎(chǔ)炮车。
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是共享的舵变、經(jīng)過驗證的狀態(tài),所以保持它在一個穩(wěn)定的狀態(tài)很重要瘦穆。
為了做到這一點纪隙,我們需要保證編譯都成功并且代碼沒有或者很少有bug出現(xiàn)。有以下經(jīng)驗可以借鑒:
- 寫Self Testing Code
- 單元測試失敗立即修復(fù)
通常測試要花費大量的時間扛或,所以可以使用 Deployment Pipeline將測試分成幾個階段绵咱。第一個階段需要盡可能地快,通常不長于10分鐘告喊,但仍要有測試力度麸拄。這一階段被稱為“commit suite”派昧,也叫做“單元測試(the unit test)”黔姜。
理想狀態(tài)下每一個提交都應(yīng)該觸發(fā)一次編譯。然后如果測試很慢蒂萎,比如是性能測試秆吵,那么這樣就不實用。
代碼運行沒有bug并不意味著就是好代碼五慈。為了保證交付地穩(wěn)定性纳寂,我們需要保證代碼地內(nèi)部質(zhì)量。一個常用地方法是 Reviewed Commits泻拦。
When to use it
每個團隊應(yīng)該在他們的開發(fā)流程中具有清晰的標準來保證分支健康毙芜,這具有巨大的價值。如果mainline健康争拐,一個開發(fā)者可以隨時開始工作只要拉取最新的分支即可腋粥。如果mainline不穩(wěn)定,需要花費大量的時間來修復(fù)分支問題架曹。
也要保證本地分支代碼健康隘冲,這樣就可以很容易合并到主干分支。
二绑雄、Integration Patterns
2.1 Mainline Integration
Developers integrate their work by pulling from mainline, merging, and - if healthy - pushing back into mainline
mainline表示了團隊軟件的當前狀態(tài)展辞。有mainline的一個好處就是簡化了集成。每個開發(fā)可以在自己本地分支上做集成万牺。
下面舉一個例子來說明罗珍,比如有個開發(fā)者叫小s洽腺,她要開發(fā)一個新的功能,用git將主干分支克隆到自己的本地倉庫:
當她在工作的時候覆旱,她的同事小V推送了一些變更到mainline已脓。由于小V在自己的分支上開發(fā),所以她并不知道mainline上的變動:
不久通殃,她想開始集成了度液。首先她要拉取mainline當前的代碼到本地,這會拉取到小V的變更画舌。由于她在本地倉庫上工作堕担,提交將在origin/master上顯示為一條單獨的代碼線。
如果小S幸運曲聂,合并小V的代碼沒有沖突霹购,否則就會有沖突需要解決。如果是文本上的沖突朋腋,源碼控制系統(tǒng)可以解決齐疙,但如果是語義上的沖突就很難解決。這時旭咽, Self Testing Code就非常有幫助贞奋。由于解決沖突會花費大量的時間,并且對代碼質(zhì)量也會產(chǎn)生風險穷绵,所以我將他們標記成黃色
這時轿塔,小S需要驗證她合并的代碼滿足mainline的健康標準。這通常意味著編譯代碼并運行所需要的測試仲墨。即使是沒有沖突的合并勾缭,也要進行編譯測試。任何提交中的錯誤都可能是由于合并導致的目养。知道這個可以幫忙她定位問題俩由,至少首先應(yīng)該從合并代碼中找到線索。
通過這種編譯和測試癌蚁,她成功地將mainline的代碼合并到自己的代碼庫幻梯,但是她還沒完成和mainline的集成。為了完成集成匈勋,她必須推送她的變更到mainline礼旅。集成包括拉取和推送。只有她完成推送后洽洁,她的工作才能開始和其他項目集成痘系。
2.2 Feature Branching
Put all work for a feature on its own branch, integrate into mainline when the feature is complete.
使用功能分支,開發(fā)者在開發(fā)一個功能特性時打開分支饿自,然后持續(xù)在這個分支上工作直到完成汰翠,最后集成到mainline龄坪。
例如,還是以小S為例复唤,她開發(fā)一個新功能:添加本地的銷售費率到他們的網(wǎng)站健田。她以當前產(chǎn)品的穩(wěn)定版本開始,拉取mainline到自己的本地佛纫,然后以當前mainline為基礎(chǔ)創(chuàng)建一個新的分支妓局。她以后一直在這個分支上開發(fā),提交了很多代碼呈宇。
She might push that branch to the project repo so that others may look at her changes.
她也可以推送這個分支到遠程倉庫好爬,這樣別的開發(fā)者也可以看到她的變更。
當她工作的時候甥啄,其他提交會在mainline存炮。所以時不時的,她會從mainline拉取新的代碼蜈漓,這樣就可以知道有哪些改變會影響她的功能穆桂。
需要注意到這并不是我上面描述的集成,是因為她并沒有推送回mainline融虽。這時她只能看到她自己的工作享完,其他人的看不見。
一些團隊喜歡讓所有的代碼都保存在遠程倉庫衣形。這時候小S就必須推送她的分支到遠程倉庫驼侠。這也允許團隊中的其他成員可以看到她當前的工作狀態(tài),即使還沒和其他人的代碼集成谆吴。
當她完成自己的功能開發(fā)時,她會執(zhí)行 Mainline Integration 來合并這個功能到產(chǎn)品中苛预。
如果小S在開發(fā)多個功能特性句狼,那么她可以為每個功能創(chuàng)建獨立的分支。
When to use it
Feature Branching在當今的工業(yè)生產(chǎn)中是一種很受歡迎的模式热某。為了談?wù)摵螘r用它腻菇,我需要介紹它的主要的替代方法- Continuous Integration。但是首先需要談?wù)擃l繁集成的作用昔馋。
2.2.1 Integration Frequency
集成的頻繁程度對一個團隊運作有很大的影響筹吐。來自State Of Dev Ops Report 的調(diào)查研究表明精英開發(fā)團隊比低績效開發(fā)團隊更頻繁的集成。這個調(diào)查符合我和大多數(shù)同行的經(jīng)驗預(yù)期秘遏。
Low-Frequency Integration
以low-frequency 案例為例丘薛,開始講述。還是以小S和小V為例邦危,她們各自開始自己的工作洋侨,拉取mainline到自己的分支舍扰,然后做了一些變更但還未提交
他們工作時希坚,其他人提交了一個變更到mainline:
這個團隊通過保證分支健康來工作个束,每次提交都會拉取mainline聊疲。小S前兩次提交沒有拉取mainline是由于mainline未發(fā)生變更售睹,但是現(xiàn)在她需要拉取M1:
我將這次merge用黃色方框表示昌妹。這次merge提交S1..3到M1飞崖。不久小V也需要做同樣的事情:
這時兩位開發(fā)者都和mainline保持同步蒜鸡,但是她們之間還未集成逢防,因為代碼都是隔離的忘朝。小S無法察覺到小V的V1..3做的變更局嘁。
小S又做了兩次提交悦昵,然后準備向mainline集成但指,這次合并很容易枚赡,因為她已經(jīng)拉取了M1的變更。
然后,小V就會有一個更復(fù)雜的體驗疲迂。當她集成到mainline時莫湘,她不得不集成S1..5到V1..6腰池。
High-Frequency Integration
在前面的例子中示弓,兩位開發(fā)者做了很多次提交后才開始集成奏属。讓我偶們看看如果每次提交就和mainline集成會發(fā)生什么囱皿。
小V第一次提交后就和mainline集成嘱腥,此時集成很容易爹橱。
小S第一次提交后也和mainline集成,但因為小V已經(jīng)提交了椭盏,所以她需要做一次merge掏颊,但由于只需要merge V1和S1,這次合并的代碼量很少盆偿。
小S的下一次集成只需要推送代碼就行了,這意味著小V的下次提交要合并小S的最近兩次提交求橄。然后罐农,這仍然是一個相對較小的merge涵亏。
當有其他人推送代碼到mainline時,小S和小V只需要按照以往的節(jié)奏合并代碼即可裆悄。
和之前一樣光稼,這次小S只需要集成S3和M1艾君,因為S1和S2已經(jīng)集成過。這意味著小G在推送M1的時候不得不集成S1..2虹茶,V1..2蝴罪。
開發(fā)者繼續(xù)剩下的工作要门,每次提交都集成:
Comparing integration frequencies
讓我們來看看上面兩種方式的區(qū)別
這里有兩個明顯的不同封豪。第一就如high-frequency integration名字所表示的那樣润脸,它比 low-frequency integration多很多次旷太,而且更重要的是每次合并的代碼量很少处嫌。越小的集成意味著越少的工作量斩个,因為沖突越少。比工作量少還要重要的是做个,風險也少了滚局。大合并的問題不在于工作本身,而在于工作本身的不確定性太闺。
大多數(shù)時候省骂,即使是大型合并也會進展順利最住,但偶爾也會進展得非常非常糟糕。偶爾的疼痛會比一般的疼痛更嚴重涨缚。如果我比較花費額外的10分鐘每個集成與1 / 50的機會花費6小時修復(fù)一個集成-我更喜歡哪一個。如果我只看努力程度兰吟,那么1 / 50更好,因為是6小時而不是8小時20分鐘茂翔。但這種不確定性讓50%的人感覺更糟揽祥,這種不確定性導致了對融合的恐懼。
我們從另一個角度來看一看兩者之間的差別檩电。如果小S和小V在第一次提交時就出現(xiàn)沖突會發(fā)生什么。在low-frequency場景下,他們直到小V的最后一次merge才會發(fā)現(xiàn)這個沖突俐末。而在high-frequency 場景下料按,在小S第一次提交時就會發(fā)現(xiàn)。
頻繁的集成增加了合并的頻率但也減少了復(fù)雜性和風險卓箫。頻繁的集成還能更快地提醒團隊發(fā)生沖突载矿。這兩者是有聯(lián)系的。令人討厭的合并通常是團隊工作中潛在的沖突的結(jié)果烹卒,只有在集成發(fā)生時才會浮出水面闷盔。
很多人沒有意識到的是源碼控制系統(tǒng)是一個交流工具逢勾。它可以讓一個開發(fā)者知道其他人正在做的事情迫摔。通過頻繁的集成纱烘,不僅可以立即知道代碼有沖突啤它,也可以知道每一個人的最新開發(fā)進展塌碌,以及代碼庫是如何演進的。我們不是孤身一人而是作為一個團隊在一起工作懊缺。