阿里技術團隊:如何提高一個研發團隊的「代碼速度」?

摘要: 螞蟻金服國際事業群技術風險部研究員南門,將和大家聊聊Code Velocity,希望能在團隊效率問題方面,為你帶來一些啟發。

什麼是代碼速度(Code Velocity)?

Code Velocity的定義是:一段代碼變更,從git裡的commit time,到在生產環境裡運行,中間經過了多少時間。換句話說,代碼從寫完開始,多快能到達生產環境。

舉個例子,C公司的一個團隊,他們今天的code velocity一般在是2-4周左右:

他們的一個典型的迭代週期是4周⁽¹⁾:第一週系分測分,第二、三週coding、testing、修bug,第三週末或第四周初合併回master、部署集成測試環境、跑回歸、上預發、上生產環境。在這樣的迭代節奏和“分支開發、主幹發佈” ⁽²⁾ 的模式裡,從commit time到進生產環境,平均是2周左右。

他們還有一些比較長週期的項目。例如,有幾個項目是四月中上旬拉的分支,一直到五月下旬才合回master,六月初發布上線。從四月上旬到五月下旬,這幾個項目分支裡的代碼沒有合回master過。這幾個項目的code velocity就比較長,平均是4周左右。

為什麼要度量和提高Code Velocity?

Code velocity體現的是一個研發團隊快速響應業務需求的能力。

以上文C公司這個團隊今天的快速響應、交付的能力水平,在兩週一次發佈窗口的節奏裡,大部分時候可能已經夠了,但一旦遇到各種意外,就捉襟見肘了,例如:臨時封網,需求變更,項目因故延期等。

快速響應、快速交付的能力要有一定的“儲備”,這就好像足球運動員要有體能儲備:要想贏下加時賽,就要有踢兩個加時賽的體能。研發團隊要能在兩週一次發佈窗口的節奏裡遊刃有餘,就要有一週一發甚至一週兩發的能力。況且,可以預見在不遠的將來,兩週一次的發佈窗口也嫌太久了,業務壓力會倒逼一週一發成為常態。那時候,這個團隊就要有“天天發”的能力,才能遊刃有餘。

研發團隊的code velocity和他們拿到的業務結果之間的關係,就像飯店上菜時間長短和生意火不火之間的關係一樣,兩者是相關的,但不是強因果關係:

有些飯店上菜挺快的,但生意不火。不能就因此說“上菜時間長短”不重要。

有些飯店,上菜很慢,但生意也還是很火。也不能因此就說“上菜時間長短”不重要。

一家飯店要火,還要看地段、裝潢、菜單、原料、廚子、服務員、宣傳等。

除了快速響應業務需求以外,提高code velocity還能幫助開發和測試同學降低項目併發、減少上下文切換、提高幸福感。在兩週一次發佈窗口的節奏下,很多時候研發同學把一個需求寫完、測完,要等其他需求,等集成環境測試,再回來搞一波,然後到了生產環境發佈再回來搞一波。事情是不連續的,開發測試其實是被打斷的。Code velocity提高了以後,開發測試有連續性,寫完了測完了的代碼就發走了,研發同學也不用身上同時揹著一串項目了。

阿里技術團隊:如何提高一個研發團隊的“代碼速度”?

為什麼Code Velocity快不起來?

仔細想想,一段代碼從git commit到生產環境,這個過程中時間大部分是花在等待上的:等著和其他代碼一起發佈上線。之所以會要把很多代碼合到一起,每兩週發一次,是出於cost vs. benefit的權衡:

每次常規發佈,不管payload(即發佈的代碼量)有多大,有些固定工作是逃不掉的:

首先,由於採取了“分支開發、主幹發佈”的模式,代碼要從各個項目分支和迭代分支合併回master,要解決衝突,確保合併時沒有漏代碼。

然後,要對master裡的代碼跑一次全量的迴歸:準備環境、部署代碼和配置、執行迴歸測試用例、分析結果。這個過程做一遍,短則半天一天,長則兩三天甚至更長。如果發現問題,需要修bug,這個過程還要再重複。

與此同時,有些團隊還要寫發佈計劃,詳細列出發佈的步驟:要改哪些配置,各個系統的發佈順序是什麼,回滾的步驟是什麼,等等。發佈計;劃寫好了還要評審。

最後,要走一遍發佈流程:先上預發,上去以後QA要做預發驗證;上生產環境,按照發布計劃一步步做,藍綠切流的過程中要讓各個系統的owner確認OK,再繼續藍綠切流。整個發佈過程需要很多人的協同。

在某些項目中,把代碼拆成小塊分多次發佈會增加開發的難度和工作量。

例如,X系統的API增加了一個新參數,要求Y系統在調用這個API的時候必須要傳這個參數。如果兩個系統上的代碼變更一起發(而且是藍綠髮布),就比較簡單。但如果把這個工作拆解成小塊,開發工作就變複雜了:X的API新增的這個參數必須先做成optional的,等Y那邊的代碼改好發上線了以後,再把X的這個新參數改成required。

另外,在有些實際項目中,實際情況比上面舉的這個例子更復雜,並不是那麼容易一眼就能看出來怎麼拆解的。

如何提高Code Velocity?

要提高code velocity,就要對上面提到的這些原因對症下藥,提升四個關鍵能力:

能頻繁地把代碼合回master

非常強大的跑回歸的能力

一鍵部署乃至無人值守發佈的能力

把大項目拆成小項目做的能力

提高code velocity,要實現質的飛躍,第一個能力“能頻繁的把代碼合回master”是關鍵抓手。把這個能力建設好了,提升code velocity的四個關鍵能力中的三個就具備了,因為“能頻繁地把代碼合回master”有三個前置條件:

實行了代碼門禁

有非常強大的跑回歸的能力(即上面四個關鍵能力的第二個)

把大項目拆成小項目做的能力(即上面四個關鍵能力的第四個)

代碼門禁(Gated Checkin)

代碼門禁能夠確保每一個進入主分支⁽³⁾的commit都達到了一定的質量標準,例如:編譯必須通過,單元測試和接口測試必須通過,新代碼的覆蓋率不能低於某個水平,靜態代碼掃描必須通過,等等。其實今天很多公司已經有post-checkin的CI在跑這些檢查項了。代碼門禁看似平淡無奇,無非就是把這些檢查項從post-checkin挪到了pre-checkin。但別小看這一挪,它的效果,不亞於把“當月業績決定本月提成”改成“當月業績決定下月提成”的效果。

代碼門禁是很典型的“測試左移”的做法,和我們對質量的基本規律的認知也是一致的:問題發現得越早,修復起來代價越小。實施了代碼門禁後,能確保主分支常年處於良好狀態。代碼門禁實施起來也很容易,很多開源和商用的CI/CD平臺都支持,例如GitLab+Jenkins。

只要做得好,代碼門禁是不會降低工程師的日常效率的。“做得好”的標準是:

執行時間:一般能接受的是10-20分鐘,95%的情況下不應超過30分鐘,否則體感就不好了。

False negative率:也就是說,代碼門禁如果失敗,有多少比例是因為代碼(包括測試用例代碼)本身的確有問題,有多少是因為代碼門禁的infrastructure的問題(比如,底層機器的資源和穩定性)。一般來說,要把false negative率控制在5%以下。False negative率如果達到20%-30%(也就是說,五次失敗裡面就有一次失敗是跟提交的代碼變更無關的),團隊裡面就會開始怨聲載道了。

非常強大的跑回歸的能力

有了強大的迴歸能力,就能在代碼頻繁的合併回master的情況下,仍然保持master分支處於可發佈狀態或者接近可發佈的狀態,有了強大的迴歸能力,我們甚至可以把一小部分的迴歸放到代碼門禁裡面去跑,那將會進一步有助於保持master分支處於可發佈狀態。

迴歸能力的強大體現在以下幾方面:

無人值守:準備環境、部署代碼和配置、執行測試、拿回結果,整個過程都必須沒有任何人的參與。

頻次:跑回歸不嫌多,最理想的是每次CI都跑回歸,那樣發現問題更早、定位問題更精確。

覆蓋率:主要是業務覆蓋率⁽⁴⁾。

穩定性:很高的通過率,很低的噪音率,結果非常repeatable。

執行時間:也許6小時和4小時看上去沒有什麼大差別,其實是有本質區別的。如果迴歸跑一遍要6小時,那麼“改代碼-跑回歸-看結果”這個過程一天只能幹兩輪;但如果迴歸一遍只要4小時,那麼這個過程一天就能幹三輪。如果能再縮短到2小時,一天就能幹六七輪。

這幾方面的迴歸能力相互之間是相輔相成的,能夠形成正循環,產生“飛輪效應”:

迴歸的運行,只有真正做到了無人值守,才有可能長期高頻次運行。

高頻次的運行,可以充分暴露各種穩定性問題,提高迴歸的穩定性。

縮短執行時間,一方面可以縮短“反饋弧”,加速各種穩定性問題的修復,另一方面可以提高測試環境的“週轉率”,在不增加硬件成本的前提下實現更高頻次的迴歸。

提高了穩定性,可以縮短用於分析迴歸結果的時間。如果一個有5,000個用例的迴歸用例集只有90%的通過率,那每次跑完迴歸有500個失敗的用例需要分析

⁽ ⁵⁾。但如果通過率有99%,那就只有50個用例需要分析了。

強大的迴歸能力的背後需要的支撐能力是:

優質的測試環境:要在預算允許的範圍內,確保測試環境的穩定和資源充沛,這樣才能支撐起迴歸的穩定性和高頻次執行。

配置代碼化(configuration-as-code)的能力。今天常見的web-based centralized配置變更管理模式不足以支持高頻詞、高併發的迴歸運行模式。實現了配置代碼化,才能實現快速的環境部署,以及在不同的環境之間用不同的配置跑回歸。配置代碼化並不是簡單地把配置寫在config文件裡面,和代碼一起打包發佈。配置代碼化是對這種config文件做法的否定之否定:配置可以在git裡面修改;配置也可以在配置管理系統裡面直接修改,變更會回沉到git裡面。部署的時候,部署工具會把git裡面的配置值以增量的方式推到配置管理系統裡面。

把大項目拆成小項目做的能力

如前所述,把代碼拆成小塊分多次發佈,的確是會增加開發的工作量的。有不少開發同學不理解為什麼要這樣做。增加了這些工作量,能讓我們的研發模式更加敏捷。這個代價是值得付出的,這些額外的時間是值得花的。

大項目拆成小項目做的一些常見套路包括:

分兩部走:先向下兼容,再去掉兼容性。這就是前文舉的那個例子:X系統的API增加了一個新參數,要求Y系統在調用這個API的時候必須要傳這個參數。拆成小項目的拆解方法是:首先,X的API新增的這個參數做成optional的,把X發佈上線。然後等Y那邊的代碼改好發上線了以後,再把X的這個新參數改成required,再發布一次X。或者,也可以用一個feature flag來控制這個新參數是否required。

Feature flag:有了feature flag,新功能的代碼寫了一半也沒關係,可以把feature flag關掉,就算代碼發上線了也不會被執行到。有時候,有些新功能所需要的代碼變更是改動在老代碼裡面的。這樣的代碼變更無法用feature flag來屏蔽。但這也沒關係,因為我們有強大的迴歸能力,能盡我們所能確信這些的代碼變更至少不會break老功能、不會在發上線後造成故障。Anyway, 哪怕不是為了把大項目拆成小項目,feature flag也是需要的。Feature flag、白名單等都是很常見的continuous delivery手段。

Capability probing:很多新功能涉及整條鏈路上各個系統的改造。現在往往上游系統的發佈依賴於下游系統的發佈。解耦這種依賴關係的一種方法是讓每個系統都通過一個統一的API接口來暴露自己當前的能力。這樣,上游系統可以判斷下游系統當前是否支持某個新功能所需要的能力Foo(例如,某種支付渠道),根據結果走不同的code path。

按域獨立發佈也是一種很成熟的拆分的方法。按域獨立發佈,實現域和域之間的解耦,能減少每次發佈的系統的數量,降低發佈風險,增加發布的靈活度。

大項目拆成小項目,還需要有比較強的需求拆分的能力:能夠把一個全鏈路級別的需求文檔拆分成域級別、系統級別的需求,這樣每個域、每個系統可以“分而治之”。

阿里技術團隊:如何提高一個研發團隊的“代碼速度”?

Code Velocity和質量、線上穩定性的關係

從上面的分析可以看出來,提高code velocity並不是以犧牲質量為代價的。上面這些提高code velocity的手段,並沒有cut corner,並沒有降低質量標準,並沒有比今天少執行任何測試。即便是頻繁的把代碼合回master,即便是把大項目拆成小項目做,該運行的各種驗證和測試還是繼續運行。而且,為了要提高code velocity,實行了代碼門禁,建設了強大的跑回歸的能力,反而是對質量有提高作用的。

提高code velocity也並不會降低線上穩定性。把大項目拆成小項目做、更加頻繁的發佈小塊代碼,能夠降低單次發佈的風險;發佈中如果出了問題,因為payload小,排查和回滾也更方便。另外,在投入資源提高code velocity的同時,我們不會降低對故障發現能力、止血能力、應急能力、監控核對等能力的投入。提高code velocity不會導致線上技術風險防控體系變弱。

將來

如果一個團隊的“能頻繁的把代碼合回master”的能力做得足夠好了,就可以完全拋棄項目分支和迭代分支,每一個commit都直接checkin進master,而且master分支每天都有若干個可以發佈的版本⁽⁶⁾,每個版本都可以用一個不同的release分支來保存。這就是所謂的“主幹開發、分支發佈”(Trunk-based Development)模式了。

到那時候,就有做到“天天發”的能力了。那時候,代碼從commit到上線可能平均只需要兩三天時間。那時候,因為有了“天天發”的能力,甚至連緊急發佈都不怎麼需要了。

如果你希望加入螞蟻金服國際事業群,可以隨時與我們直接聯繫。Java開發、測試開發、SRE工程師和工具開發等崗位虛位以待,有興趣的童鞋可發簡歷至:

[email protected]

【注】

1.一般會有兩個為期四周的迭代並行,每個迭代有自己的目標發佈窗口。發佈窗口一般是每兩週一次。

2.“分支開發、主幹發佈”的開發模式來自於A successful Git branching model。但這種模式在實踐中是有不少問題的(參見A succesful Git branching model considered harmful)。更好的模式是“主幹開發、分支發佈”(aka. Trunk-based Development)

3.主分支可以是master,也可以是項目分支或者迭代分支。

4.單元測試和接口測試看代碼覆蓋率,迴歸測試看業務覆蓋率。這在行業內的一部分開發和測試之間已經形成共識了。

5.當然,我們可以用技術的手段使得分析500個失敗的用例變得更容易。但這並不應該成為我們不去提高通過率的理由。

6.版本:對於“大庫模式”(monolithic repo)來說就是一個commit,對於“小庫模式”來說就是每個repo的一個commit構成的一個“截面”。

鏈接:https://www.jianshu.com/p/ad2229ee8bd8

來源:簡書


分享到:


相關文章: