- 深入核心的敏捷開發(fā):ThoughtWorks五大關(guān)鍵實(shí)踐
- 肖然 張凱峰
- 19字
- 2019-12-20 20:32:55
第5章 基于持續(xù)集成和測(cè)試前置的質(zhì)量?jī)?nèi)建
關(guān)于Gitflow
什么是Gitflow?Gitflow是基于Git的強(qiáng)大分支能力所構(gòu)建的一套軟件開發(fā)工作流,最早由德里森(Vincent Driessen)在2010年提出。最有名的大概是下面這張圖。
在Gitflow的模型里,軟件開發(fā)活動(dòng)基于不同的分支。
- 主要分支
- master分支上的代碼隨時(shí)可以部署到生產(chǎn)環(huán)境。
- develop作為每日構(gòu)建的集成分支,到達(dá)穩(wěn)定狀態(tài)時(shí)可以發(fā)布并merge回master。
- 支持性分支
- feature分支每個(gè)新特性都在獨(dú)立的feature分支上進(jìn)行開發(fā),并在開發(fā)結(jié)束后merge回develop。
- release分支為每次發(fā)布準(zhǔn)備的release candidate,在這個(gè)分支上只進(jìn)行bug fix,并在完成后merge回master和develop。
- hotfix分支用于快速修復(fù),在修復(fù)完成后merge回master和develop。

Gitflow工作流
Gitflow通過(guò)不同分支間的交互規(guī)劃了一套軟件開發(fā)、集成和部署的工作流。聽起來(lái)很棒,迫不及待想試試了?等等,讓我們先看看Gitflow不是什么。
- Gitflow不是Git社區(qū)的官方推薦工作流。是的,不要被名字騙到,這不是Linux內(nèi)核開發(fā)的工作流也不是Git開發(fā)的工作流。這是最早由網(wǎng)頁(yè)開發(fā)人員德里森(Vincent Driessen)和他所在的組織采用并總結(jié)出的一套工作流程。
- Gitflow也不是Github所推薦的工作流。Github對(duì)Gitflow里的某些部分有不同看法,他們利用簡(jiǎn)化的分支模型和Pull Request構(gòu)建了適合自己的工作流Github Flow。
- 在我看來(lái),Github在企業(yè)軟件開發(fā)中甚至不是一個(gè)最佳實(shí)踐。ThoughtWorks Technology Radar在2011年7月刊和2015年1月刊里多次提到Gitflow背后的feature分支模型在生產(chǎn)實(shí)踐中的危害,又在2015年11月刊里專門將Gitflow列為不被推薦的技術(shù)。
為什么Gitflow有問(wèn)題
Gitflow對(duì)待分支的態(tài)度就像“我們來(lái)建分支吧,只因?yàn)椤覀兛梢裕 保↙et’s create branches just because... we can!)
很多人吐槽,為什么開發(fā)一個(gè)新feature非得新開一個(gè)分支,而不是直接在develop上進(jìn)行,難道就是為了……廢棄掉未完成的feature時(shí)刪除一個(gè)分支比較方便?
很多人詬病Gitflow太復(fù)雜。將這么一套復(fù)雜的流程應(yīng)用到團(tuán)隊(duì)中,不僅需要每個(gè)人都能正確地理解和選擇正確的分支進(jìn)行工作,還對(duì)整個(gè)團(tuán)隊(duì)的紀(jì)律性提出了很高的要求。畢竟,規(guī)則越復(fù)雜,應(yīng)用起來(lái)就越困難。很多團(tuán)隊(duì)可能不得不借助于額外的幫助腳本去應(yīng)用這一套復(fù)雜的規(guī)則。
然而,最根本的問(wèn)題在于Github背后這一套feature分支模型。
VCS里的分支本質(zhì)上是一種代碼隔離的技術(shù)。使用feature分支時(shí),通常的做法是這樣的:當(dāng)開發(fā)人員開始一個(gè)新feature,基于develop分支的最新代碼建立一個(gè)獨(dú)立分支,然后在該分支上完成feature的開發(fā)。開發(fā)不同feature上的開發(fā)人員因?yàn)楣ぷ髟诒舜烁綦x的分支上,相互之間的工作不會(huì)有影響,直到feature開發(fā)完成,將feature分支上的代碼merge回develop分支。
我們能看到feature分支有兩個(gè)最明顯的好處。第一,各個(gè)feature之間的代碼是隔離的,可以獨(dú)立地開發(fā)、構(gòu)建、測(cè)試;第二,當(dāng)feature的開發(fā)周期長(zhǎng)于release周期時(shí),可以避免未完成的feature進(jìn)入生產(chǎn)環(huán)境。后面我們會(huì)看到,前者所帶來(lái)的傷害大于其好處,后者也可以通過(guò)其他的技術(shù)來(lái)實(shí)現(xiàn)。
合并就是合并
說(shuō)到分支就不得不提起合并。合并代碼總是痛苦和易錯(cuò)的。在軟件開發(fā)的世界里,如果一件事很痛苦,那就頻繁地去做它。比如集成很痛苦,那我們就每夜build或持續(xù)集成(continuous integration),比如部署很痛苦,那我們就頻繁發(fā)布或持續(xù)部署(continuous deployment)。合并也是一樣。所有的git教程和git工作流都會(huì)建議你頻繁地從master pull代碼,早做合并。
然而,feature分支這個(gè)實(shí)踐本身阻礙了頻繁的合并:因?yàn)椴煌琭eature分支只能從master或develop分支pull代碼,而在較長(zhǎng)周期的開發(fā)完成后才被合并回到master。也就是說(shuō)相對(duì)不同的feature分支,develop上的代碼永遠(yuǎn)是過(guò)時(shí)的。如果feature開發(fā)的平均時(shí)間是一個(gè)月,feature A所基于的代碼可能在一個(gè)月前已經(jīng)被feature B所修改掉了,這一個(gè)月來(lái)一直是基于錯(cuò)誤的代碼進(jìn)行開發(fā),而直到feature分支B被合并回develop才能獲得反饋,到最后合并的成本是非常高的。
現(xiàn)代的分布式版本控制系統(tǒng)在處理合并的能力上有很大的提升。大多數(shù)基于文本的沖突都能被git檢測(cè)出來(lái)并自動(dòng)處理,然而面對(duì)哪怕最基本的語(yǔ)義沖突上,Git仍是束手無(wú)策。在同一個(gè)codebase里使用IDE進(jìn)行rename是一件非常簡(jiǎn)單安全的事情。如果分支A對(duì)某函數(shù)進(jìn)行了rename,與此同時(shí)重命名另一個(gè)獨(dú)立的分支仍然使用舊的函數(shù)名稱進(jìn)行大量調(diào)用,在兩個(gè)分支進(jìn)行合并時(shí)就會(huì)產(chǎn)生無(wú)法自動(dòng)處理的沖突。
如果連重命名這么簡(jiǎn)單的重構(gòu)都可能面臨大量沖突,團(tuán)隊(duì)就會(huì)傾向于少做重構(gòu)甚至不做重構(gòu)。最后,代碼的質(zhì)量只能是每況愈下,逐漸腐爛。
持續(xù)集成
如果feature分支要在feature開發(fā)完成才被合并回develop分支,那我們?nèi)绾巫龀掷m(xù)集成呢?畢竟持續(xù)集成不是自己在本地把所有測(cè)試跑一遍,持續(xù)集成是把來(lái)自不同開發(fā)不同團(tuán)隊(duì)的代碼集成在一起,確保能構(gòu)建成功通過(guò)所有的測(cè)試。按照持續(xù)集成的紀(jì)律,本地代碼必須每日進(jìn)行集成,我想大概有下面幾種方案。
1. 每個(gè)feature在一天內(nèi)完成,然后集成回develop分支。這恐怕是不太可能的。況且,每個(gè)feature如果能在一天內(nèi)完成,為啥還專門開一個(gè)分支?
2. 每個(gè)分支有自己獨(dú)立的持續(xù)集成環(huán)境,在分支內(nèi)進(jìn)行持續(xù)集成。然而,為每個(gè)環(huán)境準(zhǔn)備單獨(dú)的持續(xù)集成環(huán)境需要額外的硬件資源和虛擬化能力,假設(shè)這點(diǎn)沒(méi)有問(wèn)題,不同分支間如果不進(jìn)行集成,仍然不算是真正意義上的持續(xù)集成,到最后,注定會(huì)有很大的沖突(big bang conflict)勢(shì)必?zé)o法避免。
3. 每個(gè)分支有自己獨(dú)立的持續(xù)集成環(huán)境,在分支內(nèi)進(jìn)行持續(xù)集成,同時(shí)每日將不同分支合并回develop分支進(jìn)行集成。聽起來(lái)很完美,不同分支間的代碼也可以持續(xù)集成了。可發(fā)生沖突和CI掛掉后,誰(shuí)來(lái)搞起呢?也就是說(shuō)我們還是得關(guān)心其他開發(fā)和其他團(tuán)隊(duì)的開發(fā)情況。不是說(shuō)好了用feature分支就可以不管他們自己玩嗎,那我們要feature分支還有什么用呢?
所以你會(huì)發(fā)現(xiàn),在堅(jiān)持持續(xù)集成實(shí)踐的情況下,feature分支是非常矛盾的。持續(xù)集成在鼓勵(lì)更加頻繁的代碼集成和交互,讓沖突越早解決越好。feature分支的代碼隔離策略卻在盡可能推遲代碼的集成。延遲集成所帶來(lái)的惡果在軟件開發(fā)的歷史上已經(jīng)出現(xiàn)過(guò)很多次,每個(gè)團(tuán)隊(duì)自己寫自己的代碼是挺嗨(high),到最后不同團(tuán)隊(duì)進(jìn)行聯(lián)調(diào)集成的時(shí)候就傻眼了,經(jīng)常出現(xiàn)寫兩個(gè)月代碼,花一個(gè)月時(shí)間集成的情況,質(zhì)量還無(wú)法保證。
如果不用Gitflow呢?
如果不用Gitflow,我們應(yīng)該使用什么樣的開發(fā)工作流?如果還沒(méi)聽過(guò)Trunk-Based Development,那你應(yīng)該先了解一下,趕緊用起來(lái)。

基于主干的開發(fā)
是的,所有的開發(fā)工作都在同一個(gè)master分支上進(jìn)行,同時(shí)利用持續(xù)集成確保master上的代碼隨時(shí)都是production ready的。從master上拉出release分支進(jìn)行release的追蹤。
可是feature分支可以確保沒(méi)完成的feature不會(huì)進(jìn)入到生產(chǎn)部署呀!沒(méi)關(guān)系,F(xiàn)eature Toggle技術(shù)也可以幫你做到這一點(diǎn)。如果系統(tǒng)有一項(xiàng)很大的修改,比如替換掉目前的ORM,如何采用這種策略呢?你可以試試分支by Abstraction。我們這些策略來(lái)避免feature分支是因?yàn)楸举|(zhì)上來(lái)說(shuō),feature分支是窮人版的模塊化架構(gòu)。當(dāng)你的系統(tǒng)無(wú)法在部署時(shí)或運(yùn)行時(shí)切換feature時(shí),就只能依賴版本控制系統(tǒng)和手工合并了。
分支并不是“元兇”
雖然“長(zhǎng)命”分支(long lived)是一種不好的實(shí)踐,但分支作為一種輕量級(jí)的代碼隔離技術(shù)還是非常有價(jià)值的。比如在分布式版本控制系統(tǒng)里,我們不用再依賴某個(gè)中心服務(wù)器,可以進(jìn)行獨(dú)立的開發(fā)和commit。比如在一些探索性任務(wù)上,我們可以開啟分支進(jìn)行大膽的嘗試。
技術(shù)用得對(duì)不對(duì),還是要看具體場(chǎng)景。
- C語(yǔ)言程序設(shè)計(jì)實(shí)踐教程(第2版)
- JavaScript百煉成仙
- Apache Hive Essentials
- Getting Started with CreateJS
- Learning Informatica PowerCenter 10.x(Second Edition)
- 算法精粹:經(jīng)典計(jì)算機(jī)科學(xué)問(wèn)題的Python實(shí)現(xiàn)
- WebRTC技術(shù)詳解:從0到1構(gòu)建多人視頻會(huì)議系統(tǒng)
- Python Data Analysis Cookbook
- Learning Laravel's Eloquent
- RESTful Web Clients:基于超媒體的可復(fù)用客戶端
- Emotional Intelligence for IT Professionals
- HTML5移動(dòng)前端開發(fā)基礎(chǔ)與實(shí)戰(zhàn)(微課版)
- Mobile Forensics:Advanced Investigative Strategies
- C/C++代碼調(diào)試的藝術(shù)(第2版)
- Apache Solr for Indexing Data