天天看點

Gitflow有害論

什麼是Gitflow

Gitflow是基于Git的強大分支能力所建構的一套軟體開發工作流,最早由Vincent Driessen在2010年提出。最有名的大概是下面這張圖。

Gitflow有害論

在Gitflow的模型裡,軟體開發活動基于不同的分支:

  • The main branches
    *   master 該分支上的代碼随時可以部署到生産環境
               
    • develop 作為每日建構的內建分支,到達穩定狀态時可以釋出并merge回master
  • Supporting branche
    *   Feature branches 每個新特性都在獨立的feature branch上進行開發,并在開發結束後merge回develop
               
    • Release branches 為每次釋出準備的release candidate,在這個分支上隻進行bug fix,并在完成後merge回master和develop
    • Hotfix branches 用于快速修複,在修複完成後merge回master和develop

Gitflow通過不同分支間的互動規劃了一套軟體開發、內建、部署的工作流。聽起來很棒,迫不及待想試試了?等等,讓我們先看看Gitflow不是什麼。

  • Gitflow不是Git社群的官方推薦工作流。是的,不要被名字騙到,這不是Linux核心開發的工作流也不是Git開發的工作流。這是最早由Web developer Vincent Driessen和他所在的組織采用并總結出的一套工作流程。
  • Gitflow也不是Github所推薦的工作流。Github對Gitflow裡的某些部分有不同看法,他們利用簡化的分支模型和Pull Request建構了适合自己的工作流Github Flow。
  • 現在我要告訴你,Gitflow在企業軟體開發中甚至不是一個最佳實踐。ThoughtWorks Technology Radar在2011年7月刊,2015年1月刊裡多次表明了Gitflow背後的feature branch模型在生産實踐中的危害,又在2015年11月刊裡專門将Gitflow列為不被推薦的技術。

為什麼Gitflow有問題

Gitflow對待分支的态度就像: Let's create branches just because... we can!

很多人吐槽,為什麼開發一個新feature非得新開一個branch,而不是直接在develop上進行,難道就是為了……廢棄掉未完成的feature時删除一個branch比較友善?

很多人诟病Gitflow太複雜。将這麼一套複雜的流程應用到團隊中,不僅需要每個人都能正确地了解和選擇正确的分支進行工作,還對整個團隊的紀律性提出了很高的要求。畢竟規則越複雜,應用起來就越難。很多團隊可能不得不借助額外的幫助腳本去應用這一套複雜的規則。

然而最根本問題在于Gitflow背後的這一套feature branch模型。

VCS裡的branch本質上是一種代碼隔離的技術。使用feature branch通常的做法是:當developer開始一個新feature,基于develop branch的最新代碼建立一個獨立branch,然後在該branch上完成feature的開發。開發不同feature上的developers因為工作在彼此隔離的branch上,互相之間的工作不會有影響,直到feature開發完成,将feature branch上的代碼merge回develop branch。

我們能看到feature branch最明顯的兩個好處是:

  1. 各個feature之間的代碼是隔離的,可以獨立地開發、建構、測試;
  2. 當feature的開發周期長于release周期時,可以避免未完成的feature進入生産環境。

後面我們會看到,第一點所帶來的傷害要大于其好處,第二點也可以通過其他的技術來實作。

merge is merge

說到branch就不得不提起merge。merge代碼總是痛苦和易錯的。在軟體開發的世界裡,如果一件事很痛苦,那就頻繁地去做它。比如內建很痛苦,那我們就nightly build或continuous integration,比如部署很痛苦,那我們就頻繁釋出或continuous deployment。 merge也是一樣。所有的git教程和git工作流都會建議你頻繁地從master pull代碼,早做merge。

然而feature branch這個實踐本身阻礙了頻繁的merge: 因為不同feature branch隻能從master或develop分支pull代碼,而在較長周期的開發完成後才被merge回master。也就是說相對不同的feature branch,develop上的代碼永遠是過時的。如果feature開發的平均時間是一個月,feature A所基于的代碼可能在一個月前已經被feature B所修改掉了,這一個月來一直是基于錯誤的代碼進行開發,而直到feature branch B被merge回develop才能獲得回報,到最後merge的成本是非常高的。

現代的分布式版本控制系統在處理merge的能力上有很大的提升。大多數基于文本的沖突都能被git檢測出來并自動處理,然而面對哪怕最基本的語義沖突上,git仍是束手無策。在同一個codebase裡使用IDE進行rename是一件非常簡單安全的事情。如果branch A對某函數進行了rename,于此同時另一個獨立的branch仍然使用舊的函數名稱進行大量調用,在兩個branch進行合并時就會産生無法自動處理的沖突。

如果連rename這麼簡單的重構都可能面臨大量沖突,團隊就會傾向于少做重構甚至不做重構。最後代碼的品質隻能是每況愈差逐漸腐爛。

持續內建

如果feature branch要在feature開發完成才被merge回develop分支,那我們如何做持續內建呢?畢竟持續內建不是自己在本地把所有測試跑一遍,持續內建是把來自不同developer不同team的代碼內建在一起,確定能建構成功通過所有的測試。按照持續內建的紀律,本地代碼必須每日進行內建,我想大概有這幾種方案:

  1. 每個feature在一天内完成,然後內建回develop分支。這恐怕是不太可能的。況且如何每個feature如果能在一天内完成,我們為啥還專門開一個分支?
  2. 每個分支有自己獨立的持續內建環境,在分支内進行持續內建。然而為每個環境準備單獨的持續內建環境是需要額外的硬體資源和虛拟化能力的,假設這點沒有問題,不同分支間如果不進行內建,仍然不算真正意義上的持續內建,到最後的big bang conflict總是無法避免。
  3. 每個分支有自己獨立的持續內建環境,在分支内進行持續內建,同時每日将不同分支merge回develop分支進行內建。聽起來很完美,不同分支間的代碼也可以持續內建了。可發生了沖突、CI挂掉誰來修呢,也就是說我們還是得關心其他developer和其他團隊的開發情況。不是說好了用feature branch就可以不管他們自己玩嗎,那我們要feature branch還有什麼用呢?

是以你會發現,在堅持持續內建實踐的情況下,feature branch是一件非常沖突的事情。持續內建在鼓勵更加頻繁的代碼內建和互動,讓沖突越早解決越好。feature branch的代碼隔離政策卻在盡可能推遲代碼的內建。延遲內建所帶來的惡果在軟體開發的曆史上已經出現過很多次了,每個團隊自己寫自己的代碼是挺high,到最後不同團隊進行聯調內建的時候就傻眼了,經常出現寫兩個月代碼,花一個月時間內建的情況,品質還無法保證。

如果不用Gitflow...

如果不用Gitflow,我們應該使用什麼樣的開發工作流?如果你還沒聽過Trunk Based Development,那你應該用起來了。

Gitflow有害論

是的,所有的開發工作都在同一個master分支上進行,同時利用Continuous Integration確定master上的代碼随時都是production ready的。從master上拉出release分支進行release的追蹤。

可是feature branch可以確定沒完成的feature不會進入到production呀。沒關系,Feature Toggle技術也可以幫你做到這一點。如果系統有一項很大的修改,比如替換掉目前的ORM,如何采用這種政策呢?你可以試試Branch by Abstraction。我們這些政策來避免feature branch是因為本質上來說,feature branch是窮人版的子產品化架構。當你的系統無法在部署時或運作時切換feature時,就隻能依賴版本控制系統和手工merge了。

Branch is not evil

雖然long lived branch是一種不好的實踐,但branch作為一種輕量級的代碼隔離技術還是非常有價值的。比如在分布式版本控制系統裡,我們不用再依賴某個中心伺服器,可以進行獨立的開發和commit。比如在一些探索性任務上,我們可以開啟branch進行大膽的嘗試。

技術用的對不對,還是要看上下文。

[參考文獻]

  1. Long-lived-branches-with-gitflow in radar: https://www.thoughtworks.com/radar/techniques/long-lived-branches-with-gitflow
  2. Gitflow in radar: https://www.thoughtworks.com/radar/techniques/gitflow
  3. Feature Branching in radar: https://www.thoughtworks.com/radar/techniques/feature-branching
  4. Fowler on feature branch: http://martinfowler.com/bliki/FeatureBranch.html
  5. Fowler on continuous integration: http://www.martinfowler.com/articles/continuousIntegration.html
  6. Paul Hammant on TBD: http://paulhammant.com/2015/12/13/trunk-based-development-when-to-branch-for-release/
  7. Google's Scaled Trunk Based Development: http://paulhammant.com/2013/05/06/googles-scaled-trunk-based-development/
  8. Trunk Based Development at Facebook: http://paulhammant.com/2013/03/04/facebook-tbd/
  9. Fowler on feature toggle: http://martinfowler.com/bliki/FeatureToggle.html
  10. Jez Humble on branch by abstraction:http://continuousdelivery.com/2011/05/make-large-scale-changes-incrementally-with-branch-by-abstraction/
  11. Fowler on branch by abstraction: http://martinfowler.com/bliki/BranchByAbstraction.html