阿裡妹導讀:代碼分支模式的選擇并沒有絕對的正确和錯誤之分,關鍵是與項目的規模和釋出節奏相比對。阿裡協同研發平台在經過衆多實踐曆練後,總結出了一套獨創的分支管理方法:AoneFlow,通過兼備靈活高效與簡單實用的流程,保障阿裡旗下衆多産品的傳遞。下面讓我們一起來深入了解。
引言
在阿裡内部,流行着許多有意思的工程實踐。有些實踐通過工具和流程嵌在集團的大環境裡,外界不容易複制,有些實踐則是流露在大家的日常習慣裡,被默默的遵守。比如分支管理這件事,其實屬于工具和習慣各占一半,并且頗有阿裡特色的成分,适合作為一個例子。阿裡有很多的研發團隊,不同僚業部使用的釋出流程、分支政策并非整齊劃一,但總體上看是比較規整的。其中有一種主流的釋出模式以及對應的分支使用方式,稱為“AoneFlow”。這套工作模式思路獨特,在阿裡以外的地方并不多見。本文圍繞這些實踐,聊一聊分支管理的話題。
細數分支模式
說到分支管理模式,我們最耳熟能詳的莫過于 TrunkBased 和 GitFlow。
TrunkBased 模式是持續內建思想所崇尚的工作方式,它由單個主幹分支和許多釋出分支組成,每個釋出分支在特定版本的送出點上從主幹建立出來,用來進行上線部署和 Hotfix。在 TrunkBased 模式中,沒有顯性的特性分支。當然實際上 Git 的分布式特征天生允許每個人有本地分支,TrunkBased 也并非排斥短期的特性分支存在,隻不過在說這種模式的時候,大家通常都不會明确強調它罷了。
雖然近年來有許多不錯的案例,但 TrunkBased 模式并沒有一統天下。太多的團隊同時工作在主幹上,到釋出的時候就可能出現災難(尤其是多版本并行開發的情況)。彌補的措施是 FeatureToggle 以及頻繁的內建和足夠的測試覆寫,這對開發團隊的能力提出了比較高的要求。目前 TrunkBased 模式主要用在不需要同時維護多個曆史版本的 SaaS 型項目,特别是經過微服務改造的各種小型服務上。
TrunkBased 模式有兩種常見演進版本。OneFlow 模式參考了 TrunkBased 的許多思想,對操作流程做了更嚴格的定義,增加了 Hotfix 分支等内容。多主幹模式(通常是雙主幹,固定的開發分支和固定的釋出分支),算是 TrunkBased 采用固定釋出分支的特例。
GitFlow 模式是若幹模式的集大成者,包含一個主幹分支、一個開發分支、許多的特性分支、許多的釋出分支和 Hotfix 分支,以及許多繁瑣的合并規則。它有一個 Git 插件,不過早就沒人維護了。由于對每個階段的每項操作定義十分明确,它曾經是很多重視流程的企業眼裡的香馍馍。但它使用起來并不是很容易,大量的合并沖突和對內建測試不友好也是它被诟病最多的地方。
對,還有 GithubFlow 模式,不過這種政策無非是在 TrunkBased 的基礎上,增加了個人倉庫和 Pull Request 合并代碼的操作,與在同一個倉庫裡增加個人分支的做法類似,從實用的意義來說,它更合适分布式團隊。GithubFlow 也有演進版本,例如強調了多環境部署和将倉庫或分支與環境關聯的 GitlabFlow 模式。
當然,代碼分支模式的選擇并沒有絕對的正确和錯誤之分,關鍵是與項目的規模和釋出節奏相比對。
另辟蹊徑的 AoneFlow
在 AoneFlow 上你能看到許多其他分支模式的影子。它基本上兼顧了 TrunkBased 的“易于持續內建”和 GitFlow 的“易于管理需求”特點,同時規避掉 GitFlow 的那些繁文缛節。
看一下具體套路。AoneFlow 隻使用三種分支類型:主幹分支、特性分支、釋出分支,以及三條基本規則。
規則一,開始工作前,從主幹建立特性分支。
AoneFlow 的特性分支基本借鑒 GitFlow,沒有什麼特别之處。每當開始一件新的工作項(比如新的功能或是待解決的問題)的時候,從代表最新已釋出版本的主幹上建立一個通常以feature/字首命名的特性分支,然後在這個分支上送出代碼修改。也就是說,每個工作項(可以是一個人完成,或是多個人協作完成)對應一個特性分支,所有的修改都不允許直接送出到主幹。

image
規則二,通過合并特性分支,形成釋出分支。
AoneFlow 的釋出分支設計十分巧妙,可謂整個體系的精髓。GitFlow 先将已經完成的特性分支合并回公共主線(即開發分支),然後從公共主線拉出釋出分支。TrunkBased 同樣是等所有需要的特性都在主幹分支上開發完成,然後從主幹分支的特定位置拉出釋出分支。而 AoneFlow 的思路是,從主幹上拉出一條新分支,将所有本次要內建或釋出的特性分支依次合并過去,進而得到釋出分支。釋出分支通常以release/字首命名。
這條規則很簡單,不過實際的玩法就相當豐富了。
首先,釋出分支的用途可以很靈活。基礎玩法是将每條釋出分支與具體的環境相對應,比如release/test分支對應部署測試環境,release/prod分支對應線上正式環境等等,并與流水線工具相結合,串聯各個環境上的代碼品質掃描和自動化測試關卡,将産出的部署包直接釋出到相應環境上。進階點的玩法是将一個釋出分支對應多個環境,比如把灰階釋出和正式釋出串在一起,中間加上人工驗收的步驟。進階的玩法呢,要是按疊代計劃來關聯特性分支,建立出以疊代演進的固定釋出分支,再把一系列環境都串在這個釋出分支的流水線上,就有點經典持續內建流水線的味道了。再或者做一個将所有特性分支都關聯在一起的釋出分支,專門用于對所有送出做內建測試,就玩出了 TrunkBased 的效果。當然,這些花哨的進階玩法是我臆想的,阿裡的釋出分支一般都還是比較中規中矩。
其次,釋出分支的特性組成是動态的,調整起來特别容易。在一些市場瞬息萬變的網際網路企業,以及采用“靈活運作”的乙方企業經常會遇到這種情況,已經完成就等待上線的需求,随時可能由于市場政策調整或者甲方的一個臨時決定,其中某個功能忽然要求延遲釋出或者幹脆不要了。再或者是某個特性在上線前發現存在嚴重的開發問題,需要排除。按往常的做法,這時候就要來手工“剔代碼”了,将已經合并到開發分支或者主幹分支的相關送出一個個剔除出去,做過的同學都知道很麻煩。在 AoneFlow 的模式下,重建釋出分支隻是分分鐘的事,将原本的釋出分支删掉,從主幹拉出新的同名釋出分支,再把需要保留的各特性分支合并過來就搞定。這一系列動作能夠在很大程度上實作自動化,而且不會在倉庫留下一堆剔除代碼的記錄,幹淨無污染。
此外,釋出分支之間是松耦合的,這樣就可以有多個內建環境分别進行不同的特性組合的內建測試,也能友善地管理各個特性進入到不同環境上部署的時機。松耦合并不代表沒有相關性,由于測試環境、內建環境、預釋出環境、灰階環境和線上正式環境等釋出流程通常是順序進行的,在流程上可以要求隻有通過前一環境驗證的特性,才能傳遞到下一個環境做部署,形成漏鬥形的特性釋出流。阿裡有統一平台來自動化完成特性組合在釋出分支間的遷移,在下面講工具的部分裡會再介紹。
規則三,釋出到線上正式環境後,合并相應的釋出分支到主幹,在主幹添加标簽,同時删除該釋出分支關聯的特性分支。
當一條釋出分支上的流水線完成了一次線上正式環境的部署,就意味着相應的功能真正地釋出了,此時應該将這條釋出分支合并到主幹。為了避免在代碼倉庫裡堆積大量曆史上的特性分支,還應該清理掉已經上線部分特性分支。與 GitFlow 相似,主幹分支上的最新版本始終與線上版本一緻,如果要回溯曆史版本,隻需在主幹分支上找到相應的版本标簽即可。
除了基本規則,還有一些實際操作中不成文的技巧。比如上線後的 Hotfix,正常的處理方法應該是,建立一條新的釋出分支,對應線上環境(相當于 Hotfix 分支),同時為這個分支建立臨時流水線,以保障必要的釋出前檢查和冒煙測試能夠自動執行。但其實還有一種簡便方法是,将線上正式環境對應的釋出分支上關聯的特性分支全部清退掉,在這個釋出分支上直接進行修改,改完利用現成的流水線自動釋出。如果非得修一個曆史版本的 Bug 怎麼辦呢?那就老老實實地在主幹分支找到版本标簽位置,然後從那個位置建立 Hotfix 分支吧,不過由于阿裡的産品大多是線上 SaaS 業務,這樣的場景并不多見。
正是這些簡單的規則,組成了 AoneFlow 獨樹一幟的核心套路。
AoneFlow 中每一個看似簡單的步驟都并非憑空臆造,而是經曆大量産品團隊反複磨砺後積累下來的經驗。接下來,我會說說 AoneFlow 的技術門檻以及阿裡内部的應對之道。
AoneFlow 的體驗優化
谙熟武俠之道的人都懂得,掌握一個門派的看家武藝,除了要會招式,還得有深厚的内功和趁手的兵器。否則拿了辟邪劍譜,也隻能望譜興歎。
阿裡團隊的内功和兵器,實際上是良好的代碼習慣和齊全的配套工具。
這裡說的習慣,除了開發流程和代碼分支的管理方式以外,還包括日常開發中的一些約定俗成的規約。阿裡的許多開發規約是有“文獻”記載的,主要收錄在 《阿裡巴巴 Java 開發手冊》 裡面。它的内容現在已經公開了,是以早就不算是秘密。
(注:在阿裡技術公衆号回複“手冊”,即可免費下載下傳《阿裡巴巴 Java 開發手冊》)
舉一個具體的例子。在 AoneFlow 的流程中,每次重建釋出分支的時候都會重新合并然後編譯代碼,産生新的部署包。然而,即使代碼的内容是一樣的,如果工程中依賴了一些會改變的第三方軟體包,依然可能導緻打包出的産品行為不完全一緻。是以,在阿裡的代碼規約中就明确地指出了,用于線上釋出的代碼,不可以使用包含“SNAPSHOT 版本”(即未正式釋出版本)的依賴包,進而確定每次建構出的産物都是一緻的。類似這樣的細節還有很多,好的開發習慣是確定軟體品質的必要前提。
工具可以使得團隊協作更加平滑。雖然隻要弄懂原理,AoneFlow 中每個分支建立、合并、更改步驟使用單純的 Git 指令就能玩轉。但其中的一些操作(比如為每個釋出分支選出恰當的特性分支組合進行合并)手工執行極易出錯,而且讓團隊的個人重複這些日常瑣事的指令操作,并不是令人愉悅的事情。
在阿裡内部,使用 AoneFlow 流程的團隊基本上不用自己運作 Git 來處理分支的事情,而是由阿裡巴巴集團内部名叫 Aone 的協同研發平台(以下簡稱平台)接管。這個承擔集團 80% 産品從需求和使用者故事提出到部署上線完整研發流程的平台,内置了許多以服務元件的形式嵌入的研發提效工具,其中的釋出元件為 AoneFlow 的使用者體驗添色不少。比較顯著的輔助“功效”包括以下幾個方面。
首先是整體流程的自動化。
由于是内部工具,平台的功能高度内聚。對于項目而言,從提出原始需求,将需求拆分為任務,然後根據任務線上建立特性分支,再聚合生成釋出分支,同時根據模闆自動建立測試環境,直到後期的運維保障都可以一站式地搞定。
這個流程已經遠遠超出了代碼分支管理的範疇。但正是因為如此,平台對于 AoneFlow,向前做到了将特性分支和需求項關聯起來,確定了特性分支的命名規範性;向後做到了将釋出分支與部署行為關聯起來,確定了各環境版本來源的可靠性。打通了端到端傳遞的任督二脈。
其次是釋出分支的流水線。
作為一種流程自動化的手段,CI/CD 流水線是許多現代傳遞團隊中常見的标配實踐。在 AoneFlow 的代碼生命周期裡涉及許多分支,當這些分支被建立或更新時,往往需要伴随其他的一系列行為。流水線能夠将這些日常開發過程中的代碼分支與其所表達的深層意圖(比如送出代碼即進行內建測試)聯系起來。特别是釋出分支,AoneFlow 的每個釋出分支通常關聯具體的部署環境,當有新代碼合并進分支時,就應該及時對代碼進行檢查和部署。
理想情況下,每條不同的分支都應該有與其作用相比對的一條流水線來為它服務。AoneFlow 的釋出分支是相對固定的,是以相比 GitFlow 更易于進行持續內建。理論上任何流水線工具都能夠配合 AoneFlow 使用,不過,阿裡的統一平台提供流水線對代碼評審、安全檢查、線上部署等功能的整合,還是為 AoneFlow 在内部團隊的使用優化增色不少。
還有一項很有用的輔助是分支關聯的管理。
特性分支與釋出分支的關聯關系維護是一個 AoneFlow 特有的問題。記住每個釋出分支分别來自哪些特性分支對于需要基于現有特性組合進行改變的時候十分有意義。比如當需要将某個特性從特定釋出分支退出時,通常會将除了該特性以外的其他特性所在分支進行一次合并,以替換原有的釋出分支。人為地記錄這些資訊并不輕松,要是通過平台進行展示和輔助就會友善許多。
當某些功能組合在一個低級别的釋出環境(如內建測試環境)驗證完成後,我們希望将它的内容直接遷移到進階别的環境(如預釋出環境)對應的釋出分支上。這樣可以確定線上的版本一定是經過預發驗證的,預發的版本一定是經過內建驗證的,以此類推,使得各個釋出分支形成串聯。同樣的,使用普通的 Git 指令就能實作這個操作,隻不過用可視化工具會讓流程更加直覺。
除此以外,平台提供代碼倉庫各個分支狀況的統一展示,包括分支所對應部署環境的機器資訊、操作記錄等全都一覽無餘。正是這些“高附加值”的輔助,使得 AoneFlow 得以揚長避短,成為阿裡團隊支撐複雜項目首選的利器。
寫在最後
代碼分支模式的選擇并沒有絕對的正确和錯誤之分,關鍵是與項目的規模和釋出節奏相比對。阿裡協同研發平台在經過衆多實踐曆練後,總結出了一套獨創的分支管理方法,通過兼備靈活高效與簡單實用的流程,保障阿裡旗下衆多産品的傳遞。當你還在猶豫于琳琅滿目的分支模式,既舍不得 GitFlow 的并行特性開發,又放不下 TrunkBased 的持續內建友好時,AoneFlow 也許是一個值得考慮的選擇。