作者:董越(花名荷鋤),阿里巴巴研發效能部高階產品專家
當今典型的軟體整合釋出模式是,通過類似GitHub的Pull Request或GitLab的MergeRequest的方式管理特性分支(Feature Branch):在通過程式碼評審等方法確認一條特性分支上的改動沒問題後,將其合入整合用的分支。隨後,程式碼改動進入在整合分支上執行的持續交付流水線,直到釋出上線。
在阿里巴巴內部,儘管這種工作方式也得到了研發協同工具平臺(Aone,對外叫雲效)的支援,但廣大研發同學選擇的主流工作方式卻不是它,而是用一種被稱之為變更(全稱變更請求,英文Change Request)的物件來管理特性分支,直到釋出。
初看起來,變更與Pull/MergeRequest有不少相同點,但實際它們在理念上的差別很大。
本文詳細介紹它們的相同點和不同點,並探討使用者喜歡變更這種方式的原因,當然也會介紹相應的風險和弱點。或許閱讀本文,能給你帶來一些思考。
相同點
- 變更與Pull/MergeRequest的相同點主要在於對特性分支本身的質量和流程的控制:
- 一個變更,就像一個Pull/MergeRequest一樣,大體上對應一條特性分支。
- 在Pull/Merge Request中,可以看到這條特性分支上程式碼改動內容,進而進行程式碼評審(Code Review)。類似的,也可以以變更為粒度進行程式碼評審。
- 在特性分支上的程式碼提交,可以自動觸發持續整合工具做構建以及各種自動檢測,其結果可以在Pull/Merge Request中展現。類似的,在變更中也可以展現。
- 可以把Pull/MergeRequest上的最新程式碼構建部署到它專屬的測試環境並執行,以進行測試和除錯。在變更中也可以這樣做。
- 在Pull/Merge Request中可以設定通過的條件,比如至少兩名評審者同意,且所有的程式碼評審中發現的問題都已修復或澄清,且特性分支上的持續整合流水線執行成功。在變更中也支援類似設定。
不同點及其主要價值
變更與Pull/MergeRequest的不同之處關鍵在於,這個特性分支與其他特性分支一起整合和交付的方式。
對於Pull/MergeRequest,隨後把特性分支合併到整合用的分支,然後就沒有然後了。哦不,是然後就不再以特性分支為粒度去管理了。這條特性分支已經合入整合用的分支,其上的程式碼改動已經融入整合釋出的洪流之中,被裹挾著和其他特性分支上的程式碼改動一起前進,去闖關通過整合-釋出的各個階段(Stage)。
而變更不同。即便是已與其他變更整合,它仍然具有一定的獨立性和靈活性,在確有必要時,可針對單獨變更進行操作。下面我們通過兩個例子來詳細介紹。
第一個例子:簡化起見,假定整合交付過程有三個階段:日常整合測試、在預釋出環境測試、正式釋出。某應用的變更A到變更E共五個變更,在通過了日常整合測試這個階段後,進入了在預釋出環境測試這個階段。測試時,發現變更C有一個缺陷。這個缺陷因為受日常測試環境所限,在日常整合測試階段沒有暴露出來。經分析,變更C與其他四個變更間沒有依賴關係,不會互相影響。因此,為了讓其他四個變更的釋出儘量少受影響,決定把變更C從在預釋出環境測試這一階段中摘除出來。其他四個變更在一起再次測試驗證,此時該缺陷不再出現,這四個變更在一起通過了在預釋出環境測試階段,進而進入正式釋出階段,釋出上線。
在這個例子中,在摘除了變更C後,沒有將其他四個變更在一起再次經過日常整合測試階段,是出於兩方面考慮:一是,此時的日常整合測試環境,已經被若干新添的變更所佔用。它們的測試需要時間,而且可能也會反覆調整。把新添的變更趕出去,或者把這四個變更和新添的變更混在一起,或者讓著四個變更等著,都分別有明顯的不利之處。另一方面,A、B、D、E四個變更,它們與變更C在一起,已經通過了日常整合測試。而變更C又與它們無關,因此對它們再次進行日常整合測試,發現問題的可能性很低。測試是要講究價效比的,而不是一味追求保證產品零缺陷。出於以上原因,在具體實戰中,開發團隊就有可能根據當時實際情況,在評估後決定,在摘除了變更C後,不再將其他四個變更在一起送回日常整合環境,而是直接在預釋出環境再次測試。
第二個例子:仍假定整合交付過程有日常整合測試、在預釋出環境測試、正式釋出三個階段。某應用的變更A到變更E共五個變更,在通過了日常整合測試這個階段後,進入了在預釋出環境測試這個階段。此時,根據市場情況變化,需要對變更C所承載的新功能做出少量調整,比如頁面說明文案上改幾個字。考慮到新的修改與變更C原有內容要麼都發布,要麼都不釋出,所以為便於管理,新的修改就在變更C所在的特性分支上完成。這樣形成的變更C的最新內容,與其他四個變更在一起,在預釋出環境進行測試,通過後正式釋出。
以上兩個例子,是在傳統的整合-釋出方式基礎上,加入了一些靈活性:整合-釋出過程中,必要時可以中途撤下變更,可以中途修改完善變更。而有些團隊在使用變更時,採用了更進一步的方式:不再設整合工程師之類的角色,不再規劃統一的整合、釋出的計劃和時間點。而是每個開發同學負責自己的變更,不僅跟蹤它直到把變更的質量提升到可整合的程度,而且由開發同學自己把他負責的變更依次適時推入(也可能是自動進入)整合-釋出的各個階段,跟蹤它直到釋出上線。也就是說,儘管進入了整合-釋出階段,各個變更仍是被各自的開發者分別跟蹤和推進的:它們可能有各自的推進速率和節奏,而不會相互拖累。彼此無關的變更,只是碰巧一同使用某個測試環境,一同批量測試以提高測試效率、一同上線以避免排隊而已。據此,儘可能縮短了一個需求從開發到釋出上線的時間,並表現為相當頻繁的釋出上線。同時也契合了DevOps的理念:“誰開發誰執行”(You build it, you run it)。
這一變化趨勢其實和軟體研發的管理實踐中發生的事情類似:瀑布模型時代就不提了。隨後迭代方法取代了瀑布模型。典型的,Scrum方法中的Sprint。而更進一步,在精益方法的看板牆上,迭代被弱化,關注的焦點從每個迭代做什麼,每個迭代進入到什麼階段,演化為關注每個在製品流動到了哪個階段,以及每個階段包含的在製品總量。
類似的,在上述變更管理方法中,從關注某個整合版本進入到整合-釋出的哪個階段,演化為關注每個變更進入到整合-釋出的哪個階段,以及每個階段包含了哪些變更。
另一方面的價值
以上,介紹的是使用變更管理方式帶來的靈活性,以及因為靈活務實而帶來的效率提升。變更管理方式,在資訊記錄和跟蹤方面還有一些的好處:
要想方便地知道,本次測試、本次釋出,到底包含了哪些特性,只要看看包含了哪些變更就好了。變更本身有說明文字,變更還可以關聯需求、任務、缺陷等工作項,更詳細地說明變更的目的。而在變更之外,也沒有別的程式碼修改可以通過直接提交到整合分支等途徑溜進來。
而從變更的視角,這個變更相關的所有改動,都在該特性分支上,而不會因為多次反饋修改而散亂到各處。因此這些修改總是可以方便地一同檢視,一同操作。同時,總是能夠清晰地知道這個變更的狀態,它到了哪個階段:開發完畢了嗎?進入日常整合測試階段了嗎?已經正式釋出了嗎?等等。
變更可以關聯需求、任務、缺陷等工作項,同時變更的狀態是可以自動獲取的。因此,看板牆上跟蹤的工作項,從原理上就可能被自動移動,以反映其實際狀態。協作和進展,在看板牆上一覽無餘。
弱點和風險
以上談的都是這樣的變更管理方式能帶來的好處。那麼,它有沒有弱點和風險呢?
是的,它有。從大爆炸式整合到持續部署流水線,業界幾十年來幾乎一直在採用一個基本模式:總是一個整合版本,去順序經歷整合-釋出的各個階段。這樣可以保證,下一階段收到的內容,總是精確的經過了上一個階段的檢驗。而本文介紹的變更管理方式所引入的靈活性,意味著顛覆了這一基本模式。靈活性從來都是雙刃劍。靈活性意味著風險增加,意味著可能被濫用。
敏捷宣言認為“個體和互動高於流程和工具”,上述變更管理方式暗合了這樣的思想。但在實際使用該方式時,需要注意到它對團隊成員提出了更高的要求:要求他們在具體場景具體案例中,能夠對變更間的相關性及相應風險做出評估,並瞭解不同選擇對效率的影響,最終綜合做出特定場景特定案例中的決策。具體來說:
- 變更對應的程式碼改動越少,中途撤下變更帶來的風險越小。
- 中途修改完善變更所對應的程式碼改動越少,帶來的風險越小。
- 軟體架構越好,變更中途撤下或修改完善帶來的風險越小。
- 本次變更與其他變更的相關性越小,中途撤下或修改完善帶來的風險越小。
- 越緊急,越考慮靈活處理。
- 業務角度,對軟體質量的要求越高,就越不要考慮靈活處理。
延伸一下,事實上,在微服務甚至函式服務時代,即便不使用上述變更管理方式,也有類似上文的風險,也相應需要團隊成員具備類似上文的自主判斷能力。為什麼這麼說呢?
之所以把單體應用拆分為微服務甚至函式服務,一個重要原因就是為了每個服務能單獨測試和釋出上線。然而,在使用微服務甚至函式服務方式時,被測物件嚴格地講並不是一個服務,而是該服務以及測試環境中與其直接或間接打交道的所有其他服務。而當把每個服務單獨測試和釋出時,就經常會導致本階段測試時某個其他服務的版本,與下個階段測試時的版本不同,或者與將來正式釋出執行時的版本不同。於是就意味著類似上述變更管理方式中的風險。
相應的,這裡面就需要人來判斷(當然可以有智慧演算法的輔助),本次哪些服務上的改動務必要一起測試和上線,而另外幾個服務上的改動可以單獨運作。而下次可能又是不同情況,要根據下次的具體情況判斷。
由此看來,“總是由一個整合版本,去順序經歷整合-釋出的各個階段”這個基本模式,其實已經被悄然突破了。上述變更管理方式,只是使這個突破更加明顯了而已。
落地及工具支援
以上是介紹了一種獨特的變更管理方式,介紹了優點,也介紹了相應的風險。下面我們來看看它在阿里是如何落地的。
首先需要一套分支方案來支援它。大體上是這樣:
- master分支總是代表最新已釋出版本。
- 程式碼改動總是在特性分支上完成。特性分支總是從master分支上拉出的,並在必要時從master再次同步。
- 沒有一條長期存在的整合釋出用的分支。而是整合釋出過程的各個階段,各對應一條短期的,被自動管理的整合釋出分支。從master分支自動拉出該分支,再把各特性分支自動合併到該分支(出現衝突時人工介入),於是它上面就有了使用者想要的各特性。
- 如果發現某個特性需要進一步修改完善,在特性分支上完成,並再次合併到相應的整合釋出分支。
《在阿里,我們如何管理程式碼分支?》這篇文章對上述分支方案有更多介紹。
《雲效 > 使用指南 > 持續交付 > 開發模式 > 分支模式》這篇文件是該分支方案的詳細說明。
可以看出,這套方案,對工具平臺的要求是比較高的:從介面角度,使用者只需要管理各個整合釋出階段分別要有哪些變更。而工具平臺要將它對映為對整合釋出分支的管理,包括建立新分支或複用已有分支、從各特性分支到整合釋出分支的合併等等。這裡面也包括了不少演算法,以儘可能減少相同的合併衝突重複出現。
對工具平臺的高要求,或許是這套方法多年來一直只是在阿里巴巴內部被廣為使用的原因。
不少曾在阿里巴巴工作過的同學,出去後都念叨著沒有這樣的工具支援了。不過現在好了,就像Google基於內部的Borg對外提供了Kubernetes,阿里巴巴也基於內部研發協同工具平臺Aone對外提供了雲效。
雲效的公有云版和專有云版,都提供了上述方法的完整支援。不論是你對上述方法抱有興趣還是懷有疑慮,都可以嘗試研究一下。