天天看點

如何提高一個研發團隊的“代碼速度”?

什麼是代碼速度(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構成的一個“截面”。

原文釋出時間為:2018-07-24

本文作者:阿裡技術

本文來自雲栖社群合作夥伴“

阿裡技術

”,了解相關資訊可以關注“