廿一世紀的軟體開發有一句名言:「最好避免犯錯的方法,就是『天天犯錯』」。
以前我們很喜歡「規格」,認為不管什麼事,只要你規格開好來,我就能照規格刻給你。長久下來,你最常聽到 RD 掛在口中的話就是:
這不是 RD 的錯,這是一種文化的養成,這種文化是綜合所有 Force 後的 Context ,所塑造出來的 Form。
圖片來源:https://www.slideshare.net/teddysoft/pattern-based-problem-solvingpublished
客戶有一個待解問題,為了解決問題,大家不斷嘗試、不斷改善,終於找到一個能解決問題的 Solution,就把它寫下來留給後人,經過一段時間的驗證都沒有問題,就寫成規定裱起來,掛在辦公室大門口,形成了我們的「SOP」,也就是 RD 在這個 Form 下,自然而然演化出來的 Solution。
在外力不會變的情況下,SOP 是方便有效的,但是,在廿一世紀的現代,外力是每天都在變的,昨天的假設今天會不存在,昨天以為的需求今天必需避免。
那怎麼辦?
那就往回退一步,回到 Solution 還沒被提出前,繼續「不斷嘗試、不斷改善」,找出新 Solution,然後,外力又改變,沒關係,我們再繼續「不斷嘗試、不斷改善」…。外力不會停止改變,我們就是每天試錯,每天改善,每天都比昨天更好、更快、更能適應環境。
這,不就是敏捷嗎?
回到主題,今天要來聊兩種版本管理的分支策略: GitFlow 與 主線開發。這兩種策略都適合敏捷開發,但即便你不用敏捷開發,還是可以使用它們來管理你的程式版本。
GitFlow,圖片截自網路
GitFlow 是一種「開出去再併回來」的分枝策略。如上圖所示,GitFlow 的發明人 Vincent Driessen 根據開發過程中常見的場景,分出幾種「常駐型」分支,與「臨時型」分支,藉以幫助開發人員有條理地管理功能的開發、修復與釋出。
develop 是常駐型分支之一,它代表的是「開發完成、開發者認為沒問題的功能」,於是,在 develop 上面的每一個點,都代表著一個相對穩定的功能,或一個已被修復的錯誤。在一個產品線上,develop 隨時待命,等待成熟時機,就要合併入 master 而成為正式功能了。
feature 是一種臨時型分支,它「始於 develop,終於 develop」,當有開發任務時會從 develop 上長出來,結束後再回到 develop,一般而言,一個 feature branch 只負責一個功能,而且「越短命越好」,譬如說一兩天以內。才能避免 merge 時的「conflict 地獄」。
如果說 develop 上的點是「相對穩定」,那 master 上的點就是「很有信心」的功能了。當 Release 時機成熟,我們會從 develop 合併進 master 來,並給一個版號,在 Git 上通常就是下一個 Tag。也就是說,GitFlow 規定你,在正式環境上的系統,一定要從 master 上拿,如非必要,不可以有例外。
release 也是一個「短命」的臨時型分支,在需要 Release 時從 develop 分出來,有些人會在上面寫一些 Release Note,有些人會拿去做預產線測試,有些人則單純只是拿來當一個緩衝,求個心安…。但無論如何,沒有人會養一個長壽的 release branch 的。如果這個分支真的遇到什麼無法短時間內解決的問題,一般就會建議直接放棄 Release,看看是什麼問題,再從 develop 開 feature 出來修。當然,如果是小問題,就直接在 release branch 上修一修,再同時 merge 回 develop 與 master 就好了。
同屬臨時型分支,hotfix 跟 feature 有點像,但有兩點不同。第一,它從 master 開出來,第二,它「只能修 bug,而且是緊急的 bug」。GitFlow 規定,決不能在 hotfix 上加新功能,否則會破壞 GitFlow「以 develop 為中心」的設計精神,這點非常容易忘記,會牢記。
哇!這麼複雜?
對的,GitFlow 的五大分支的組合的確是有點複雜,作者就是希望開發者學會了以後,往後開發上遇到的狀況,都有對應的方法可以解,就不用老是隨機應變、臨場發揮了。
有人喜歡嚴謹的管理風格,就有人喜歡簡單自由。近年來流行的「主線開發」就屬於這種。
所謂的主線開發,正如其名,開發者不用開 branch,大家都在 master 上面加功能。正因為大家都在主線上,所以相較於 GitFlow,流程簡單很多,也沒有什麼硬性規定,自由度很高。
沒有 branch,那不同 RD 要怎麼同時開發多個功能?
其實這件事情,早在 Linus Torvalds 當年發明 Git 時,就已經想好了。Linus 認為,以前的版控工具,如 SVN,遠端一定要有一個中央控管的 Server 在那邊,又笨又醜。他認為,在一個團隊中,人人都應該要可以是彼此的「遠端 Server」,如此一來,我們可以今天 pull 你的 code,明天 push 到他的 repository,這樣「去中心化」、「分散式」的設計,才夠自由方便
舉例,史提夫跟戴夫同時要開發兩個功能,他們先從共用主機 origin 把 master 拉回自己電腦,開始開發。史提夫動作比較快,先做完測完並回主線,這時戴夫發現主線有變動,為了不確保自己的修改不會弄壞,於是先再拉一次 master 回來 merge,如果沒事就沒事,如果有問題,就在自己電腦上修復,修好測好再往上推,完成。
但是,如果兩人開發差不多快,無法確定誰要先 push 時該怎麼辦?這時,史提夫可以先到戴夫的 repository 去先把程式 pull 下來 merge,跑過測式,確認沒問題,自己的功能也寫完測完以後,再把兩人的 code 一起 push 到 origin 去。
在 Git 發明之初,Linus Torvalds 本來就是想要建造一個「去中心化、分散式」的版本管理工具,因此使用 Git 時,每個人都可以是彼此的 remote,每個人都可以 pull 彼此的程式,而且不管你是什麼分支,大家講好就好。
從這個角度看起來,其實「主線開發」更符合 Git 原始的精神,因為它根本就沒有 branch,就大家都是 master,彼此自由地依需求 merge 來 merge 去。
萬一開發完的功能還沒要上怎麼辦?
其實這件事不只主線開發會發生,GitFlow 也會,只是主線開發會使狀況更明顯而已。啊解決方法也不難,就是設一個 Feature Toggle,先把開關關閉,等有需要時再打開就好。
礙於篇幅,這裡對 Feature Toggle 不多做細節的說明,讀者可參考文末附上,Miles Chou 的部落格中,對 Feature Toggle 的說明與釋疑。
不論如何你用哪一種方法,有一件事筆者建議你務必要遵守:「瘦」。不管是 GitFlow 的一個 branch,還是主線開發的一個 local repository,我們都會遇到「merge」的問題,而唯一能解決此問題的方法就是,每個功能都不要太大,要經常 merge,因為經常 merge,每次 merge 時要解決的問題才會比較小,而小問題肯定比大問題好解,不管是 conflict 還是 bug。
這也點出了一個重要的觀念:「有 merge 就要跑全測試。」
每次的 merge,不管是 remote master 與 local master 的 merge,還是 feature branch 與 develop branch 的 merge,都是兩種版本交會的地方,此時就比較可能遇到「原版本開發時沒想到的問題」,因此,不論你平常多久跑一次測試,至少在 merge 的點上,你一定要跑一次全測試,才能確保正確性。
這時,單元測試的重要性就出來了。你知道 merge 的點容易出錯,所以要測,但是你又沒有單元測試,得靠人工驗測,而人工驗測又曠時費日,所以你就不敢天天 merge,然後久久 merge 一次就錯誤百出,然後就更不敢 merge…,多常見的惡性循環啊!
想打破這個惡性循環嗎?那就來寫單元測試吧,哪怕只有一個都好,就從明天開始。
Miss Ko,圖片截自網路
謎之聲:「打破他!」
ithelp2021
之後有剛寫程式不久的人(雖然我現在也是,但總有比我更菜的哈哈哈)問我我就貼這篇給他看~~~
多希望一開始學版控的時候有這篇,太讚了吧XDD