二十一世紀的軟體開發有一句名言:「最好避免犯錯的方法,就是天天犯錯。」過去我們仰賴規格文件,認為只要把規格定清楚就能照單交付,於是工程師最常掛在嘴邊的話往往是「請 PO 再去確認需求」、「這當初規格沒寫」。這不是工程師的問題,而是文化在綜合各種外力(Force)下塑造出來的形(Form)。

當外部環境穩定時,標準作業流程(SOP)確實有效;但在現代,外力天天在變,昨天的假設今天就不成立。既然如此,就只能退回「不斷嘗試、不斷改善」的階段,每天試錯、每天進步——這正是敏捷的精神。

回到主題,今天要聊的是兩種版本管理的分支策略:GitFlow 與主線開發(Trunk-Based Development)。兩者都很適合敏捷開發,即使團隊不採敏捷流程,也能用來管理程式版本。

GitFlow#

GitFlow 是由 Vincent Driessen 提出的「開分支再合回來」的策略。他根據開發過程中常見的場景,定義出幾種「常駐型」分支與「臨時型」分支,協助團隊有條理地管理功能開發、修復與發布。

develop#

常駐型分支,代表「開發完成、開發者認為沒問題」的功能。develop 上的每個提交都象徵一個相對穩定的功能或一次已修復的錯誤;當時機成熟,就會被合併進 master 成為正式版本。

feature#

臨時型分支,特性分支(Feature Branch)「始於 develop、終於 develop」。一條 feature branch 通常只負責一項功能,而且越短命越好,最好一兩天內就完成,避免合併(Merge)時掉進 conflict 地獄。

master#

如果說 develop 是「相對穩定」,那 master 就是「很有信心可以上線」。發布時機到了,會把 develop 合併進 master 並打上版本標籤(Tag)。GitFlow 規定產線上的服務必須來自 master,沒有例外。

release#

也是短命的臨時型分支,發布時機從 develop 分出來,可以拿來寫 Release Note、做預產線測試或當作緩衝。沒有人會養一條長命的 release branch;遇到無法短時間解決的問題,通常會直接放棄這次 Release,回到 develop 開新的 feature 修復。若只是小問題,就在 release branch 修一修,再同步 merge 回 develop 與 master。

hotfix#

跟 feature 類似,但有兩個差異:第一,它從 master 開出來;第二,它「只能修緊急的 bug」。GitFlow 嚴禁在 hotfix 上加新功能,否則會破壞「以 develop 為中心」的設計,這點很容易忘,要牢牢記住。

GitFlow 五大分支的組合確實有些複雜,但作者的意圖是讓開發者學會以後,遇到任何狀況都有對應的方法可循,不必每次臨場發揮。

主線開發#

有人喜歡嚴謹流程,就有人喜歡簡單自由。近年來流行的主線開發(Trunk-Based Development)就屬於後者:所有人都直接在 master 上面加功能,不開長命分支。流程簡單、彈性高,也沒有太多硬性規定。

那不同工程師同時開發多個功能時要怎麼辦?這件事其實 Linus Torvalds 當年發明 Git 時就已經想清楚了。傳統像 SVN 那樣需要中央伺服器的架構並不自由,他主張團隊裡每個人都可以是彼此的「遠端 Server」,今天 pull 你的 code,明天 push 到他的 repository,這種去中心化、分散式的設計才夠靈活。

舉例來說,史提夫和戴夫同時開發兩個功能:兩人各自從共用的 origin 把 master 拉回本機開發。史提夫先完成、先測試、先推回主線;戴夫發現主線變動,為了避免自己改壞,先把最新的 master 拉回本機 merge,沒事就推上去,有問題就在本機修好再推。如果兩人速度差不多無法決定誰先 push,史提夫也可以直接到戴夫的 repository 把 code pull 下來合併、測過、再連同自己的修改一併 push 到 origin。

從這個角度看,主線開發其實更貼近 Git 原本「去中心化、分散式」的精神:根本沒有 branch,大家都在 master 上,依需要互相 pull、merge。

那如果功能寫完還沒要上呢?這件事不只主線開發會遇到,GitFlow 也會。常見解法是用 Feature Toggle,先把開關關起來,需要時再打開即可。

共同點:分支要瘦#

不論採用哪一種策略,有件事建議務必遵守:「瘦」。不管是 GitFlow 的 feature branch,還是主線開發中本機的 repository,最終都要面對 merge。唯一能舒緩這個痛點的方法,就是讓每次 merge 的單位都小:經常 merge,每次要處理的衝突或 bug 才會比較小,小問題肯定比大問題好解。

這也帶出一個關鍵觀念:「有 merge 就要跑全測試。」

每一次 merge,無論是遠端 master 與本機 master 的合併,或 feature branch 跟 develop 的合併,都是兩個版本的整合(Integration)點,最容易冒出原本沒想到的問題。所以不管平時多久跑一次測試,至少在 merge 點上一定要跑一次完整測試,才能確保正確性。

這就是單元測試的重要性所在。明知 merge 是高風險點卻沒有單元測試,只能仰賴人工驗測;人工又慢又貴,於是不敢天天 merge,等到久久 merge 一次就錯誤百出,又更不敢 merge——典型的惡性循環。

想跳出這個循環,就從寫單元測試開始,哪怕只有一個都好,從明天開始動手吧。配合持續整合(Continuous Integration, CI)讓每次 merge 都能自動跑全測,惡性循環自然會被打破。

原文出處#

  • 原書/iThome:https://ithelp.ithome.com.tw/articles/10273819