如何提高一个研发团队的“代码速度”?
?
阿里妹導(dǎo)讀:Code Velocity(代碼速度),體現(xiàn)了一個(gè)研發(fā)團(tuán)隊(duì)快速響應(yīng)業(yè)務(wù)需求的能力。如果做得好,代碼從commit到上線可能平均只需要兩三天時(shí)間,甚至連緊急發(fā)布都不怎么需要了。
今天,螞蟻金服國(guó)際事業(yè)群技術(shù)風(fēng)險(xiǎn)部研究員南門(mén),將和大家聊聊Code Velocity,希望能在團(tuán)隊(duì)效率問(wèn)題方面,為你帶來(lái)一些啟發(fā)。
什么是代碼速度(Code Velocity)?
Code Velocity的定義是:一段代碼變更,從git里的commit time,到在生產(chǎn)環(huán)境里運(yùn)行,中間經(jīng)過(guò)了多少時(shí)間。換句話說(shuō),代碼從寫(xiě)完開(kāi)始,多快能到達(dá)生產(chǎn)環(huán)境。
舉個(gè)例子,C公司的一個(gè)團(tuán)隊(duì),他們今天的code velocity一般在是2-4周左右:
他們的一個(gè)典型的迭代周期是4周?1?:第一周系分測(cè)分,第二、三周coding、testing、修bug,第三周末或第四周初合并回master、部署集成測(cè)試環(huán)境、跑回歸、上預(yù)發(fā)、上生產(chǎn)環(huán)境。在這樣的迭代節(jié)奏和“分支開(kāi)發(fā)、主干發(fā)布” ?2? 的模式里,從commit time到進(jìn)生產(chǎn)環(huán)境,平均是2周左右。
他們還有一些比較長(zhǎng)周期的項(xiàng)目。例如,有幾個(gè)項(xiàng)目是四月中上旬拉的分支,一直到五月下旬才合回master,六月初發(fā)布上線。從四月上旬到五月下旬,這幾個(gè)項(xiàng)目分支里的代碼沒(méi)有合回master過(guò)。這幾個(gè)項(xiàng)目的code velocity就比較長(zhǎng),平均是4周左右。
為什么要度量和提高Code Velocity?
Code velocity體現(xiàn)的是一個(gè)研發(fā)團(tuán)隊(duì)快速響應(yīng)業(yè)務(wù)需求的能力。
以上文C公司這個(gè)團(tuán)隊(duì)今天的快速響應(yīng)、交付的能力水平,在兩周一次發(fā)布窗口的節(jié)奏里,大部分時(shí)候可能已經(jīng)夠了,但一旦遇到各種意外,就捉襟見(jiàn)肘了,例如:臨時(shí)封網(wǎng),需求變更,項(xiàng)目因故延期等。
快速響應(yīng)、快速交付的能力要有一定的“儲(chǔ)備”,這就好像足球運(yùn)動(dòng)員要有體能儲(chǔ)備:要想贏下加時(shí)賽,就要有踢兩個(gè)加時(shí)賽的體能。研發(fā)團(tuán)隊(duì)要能在兩周一次發(fā)布窗口的節(jié)奏里游刃有余,就要有一周一發(fā)甚至一周兩發(fā)的能力。況且,可以預(yù)見(jiàn)在不遠(yuǎn)的將來(lái),兩周一次的發(fā)布窗口也嫌太久了,業(yè)務(wù)壓力會(huì)倒逼一周一發(fā)成為常態(tài)。那時(shí)候,這個(gè)團(tuán)隊(duì)就要有“天天發(fā)”的能力,才能游刃有余。
研發(fā)團(tuán)隊(duì)的code velocity和他們拿到的業(yè)務(wù)結(jié)果之間的關(guān)系,就像飯店上菜時(shí)間長(zhǎng)短和生意火不火之間的關(guān)系一樣,兩者是相關(guān)的,但不是強(qiáng)因果關(guān)系:
- 有些飯店上菜挺快的,但生意不火。不能就因此說(shuō)“上菜時(shí)間長(zhǎng)短”不重要。
- 有些飯店,上菜很慢,但生意也還是很火。也不能因此就說(shuō)“上菜時(shí)間長(zhǎng)短”不重要。
一家飯店要火,還要看地段、裝潢、菜單、原料、廚子、服務(wù)員、宣傳等。
除了快速響應(yīng)業(yè)務(wù)需求以外,提高code velocity還能幫助開(kāi)發(fā)和測(cè)試同學(xué)降低項(xiàng)目并發(fā)、減少上下文切換、提高幸福感。在兩周一次發(fā)布窗口的節(jié)奏下,很多時(shí)候研發(fā)同學(xué)把一個(gè)需求寫(xiě)完、測(cè)完,要等其他需求,等集成環(huán)境測(cè)試,再回來(lái)搞一波,然后到了生產(chǎn)環(huán)境發(fā)布再回來(lái)搞一波。事情是不連續(xù)的,開(kāi)發(fā)測(cè)試其實(shí)是被打斷的。Code velocity提高了以后,開(kāi)發(fā)測(cè)試有連續(xù)性,寫(xiě)完了測(cè)完了的代碼就發(fā)走了,研發(fā)同學(xué)也不用身上同時(shí)背著一串項(xiàng)目了。
?
為什么Code Velocity快不起來(lái)?
仔細(xì)想想,一段代碼從git commit到生產(chǎn)環(huán)境,這個(gè)過(guò)程中時(shí)間大部分是花在等待上的:等著和其他代碼一起發(fā)布上線。之所以會(huì)要把很多代碼合到一起,每?jī)芍馨l(fā)一次,是出于cost vs. benefit的權(quán)衡:
- 每次常規(guī)發(fā)布,不管payload(即發(fā)布的代碼量)有多大,有些固定工作是逃不掉的:
首先,由于采取了“分支開(kāi)發(fā)、主干發(fā)布”的模式,代碼要從各個(gè)項(xiàng)目分支和迭代分支合并回master,要解決沖突,確保合并時(shí)沒(méi)有漏代碼。
然后,要對(duì)master里的代碼跑一次全量的回歸:準(zhǔn)備環(huán)境、部署代碼和配置、執(zhí)行回歸測(cè)試用例、分析結(jié)果。這個(gè)過(guò)程做一遍,短則半天一天,長(zhǎng)則兩三天甚至更長(zhǎng)。如果發(fā)現(xiàn)問(wèn)題,需要修bug,這個(gè)過(guò)程還要再重復(fù)。
與此同時(shí),有些團(tuán)隊(duì)還要寫(xiě)發(fā)布計(jì)劃,詳細(xì)列出發(fā)布的步驟:要改哪些配置,各個(gè)系統(tǒng)的發(fā)布順序是什么,回滾的步驟是什么,等等。發(fā)布計(jì);劃寫(xiě)好了還要評(píng)審。
最后,要走一遍發(fā)布流程:先上預(yù)發(fā),上去以后QA要做預(yù)發(fā)驗(yàn)證;上生產(chǎn)環(huán)境,按照發(fā)布計(jì)劃一步步做,藍(lán)綠切流的過(guò)程中要讓各個(gè)系統(tǒng)的owner確認(rèn)OK,再繼續(xù)藍(lán)綠切流。整個(gè)發(fā)布過(guò)程需要很多人的協(xié)同。
- 在某些項(xiàng)目中,把代碼拆成小塊分多次發(fā)布會(huì)增加開(kāi)發(fā)的難度和工作量。
例如,X系統(tǒng)的API增加了一個(gè)新參數(shù),要求Y系統(tǒng)在調(diào)用這個(gè)API的時(shí)候必須要傳這個(gè)參數(shù)。如果兩個(gè)系統(tǒng)上的代碼變更一起發(fā)(而且是藍(lán)綠發(fā)布),就比較簡(jiǎn)單。但如果把這個(gè)工作拆解成小塊,開(kāi)發(fā)工作就變復(fù)雜了:X的API新增的這個(gè)參數(shù)必須先做成optional的,等Y那邊的代碼改好發(fā)上線了以后,再把X的這個(gè)新參數(shù)改成required。
另外,在有些實(shí)際項(xiàng)目中,實(shí)際情況比上面舉的這個(gè)例子更復(fù)雜,并不是那么容易一眼就能看出來(lái)怎么拆解的。
如何提高Code Velocity?
要提高code velocity,就要對(duì)上面提到的這些原因?qū)ΠY下藥,提升四個(gè)關(guān)鍵能力:
提高code velocity,要實(shí)現(xiàn)質(zhì)的飛躍,第一個(gè)能力“能頻繁的把代碼合回master”是關(guān)鍵抓手。把這個(gè)能力建設(shè)好了,提升code velocity的四個(gè)關(guān)鍵能力中的三個(gè)就具備了,因?yàn)椤澳茴l繁地把代碼合回master”有三個(gè)前置條件:
代碼門(mén)禁(Gated Checkin)
代碼門(mén)禁能夠確保每一個(gè)進(jìn)入主分支?3?的commit都達(dá)到了一定的質(zhì)量標(biāo)準(zhǔn),例如:編譯必須通過(guò),單元測(cè)試和接口測(cè)試必須通過(guò),新代碼的覆蓋率不能低于某個(gè)水平,靜態(tài)代碼掃描必須通過(guò),等等。其實(shí)今天很多公司已經(jīng)有post-checkin的CI在跑這些檢查項(xiàng)了。代碼門(mén)禁看似平淡無(wú)奇,無(wú)非就是把這些檢查項(xiàng)從post-checkin挪到了pre-checkin。但別小看這一挪,它的效果,不亞于把“當(dāng)月業(yè)績(jī)決定本月提成”改成“當(dāng)月業(yè)績(jī)決定下月提成”的效果。
代碼門(mén)禁是很典型的“測(cè)試左移”的做法,和我們對(duì)質(zhì)量的基本規(guī)律的認(rèn)知也是一致的:問(wèn)題發(fā)現(xiàn)得越早,修復(fù)起來(lái)代價(jià)越小。實(shí)施了代碼門(mén)禁后,能確保主分支常年處于良好狀態(tài)。代碼門(mén)禁實(shí)施起來(lái)也很容易,很多開(kāi)源和商用的CI/CD平臺(tái)都支持,例如GitLab+Jenkins。
只要做得好,代碼門(mén)禁是不會(huì)降低工程師的日常效率的。“做得好”的標(biāo)準(zhǔn)是:
- 執(zhí)行時(shí)間:一般能接受的是10-20分鐘,95%的情況下不應(yīng)超過(guò)30分鐘,否則體感就不好了。
- False negative率:也就是說(shuō),代碼門(mén)禁如果失敗,有多少比例是因?yàn)榇a(包括測(cè)試用例代碼)本身的確有問(wèn)題,有多少是因?yàn)榇a門(mén)禁的infrastructure的問(wèn)題(比如,底層機(jī)器的資源和穩(wěn)定性)。一般來(lái)說(shuō),要把false negative率控制在5%以下。False negative率如果達(dá)到20%-30%(也就是說(shuō),五次失敗里面就有一次失敗是跟提交的代碼變更無(wú)關(guān)的),團(tuán)隊(duì)里面就會(huì)開(kāi)始怨聲載道了。
非常強(qiáng)大的跑回歸的能力
有了強(qiáng)大的回歸能力,就能在代碼頻繁的合并回master的情況下,仍然保持master分支處于可發(fā)布狀態(tài)或者接近可發(fā)布的狀態(tài),有了強(qiáng)大的回歸能力,我們甚至可以把一小部分的回歸放到代碼門(mén)禁里面去跑,那將會(huì)進(jìn)一步有助于保持master分支處于可發(fā)布狀態(tài)。
回歸能力的強(qiáng)大體現(xiàn)在以下幾方面:
- 無(wú)人值守:準(zhǔn)備環(huán)境、部署代碼和配置、執(zhí)行測(cè)試、拿回結(jié)果,整個(gè)過(guò)程都必須沒(méi)有任何人的參與。
- 頻次:跑回歸不嫌多,最理想的是每次CI都跑回歸,那樣發(fā)現(xiàn)問(wèn)題更早、定位問(wèn)題更精確。
- 覆蓋率:主要是業(yè)務(wù)覆蓋率???。
- 穩(wěn)定性:很高的通過(guò)率,很低的噪音率,結(jié)果非常repeatable。
- 執(zhí)行時(shí)間:也許6小時(shí)和4小時(shí)看上去沒(méi)有什么大差別,其實(shí)是有本質(zhì)區(qū)別的。如果回歸跑一遍要6小時(shí),那么“改代碼-跑回歸-看結(jié)果”這個(gè)過(guò)程一天只能干兩輪;但如果回歸一遍只要4小時(shí),那么這個(gè)過(guò)程一天就能干三輪。如果能再縮短到2小時(shí),一天就能干六七輪。
這幾方面的回歸能力相互之間是相輔相成的,能夠形成正循環(huán),產(chǎn)生“飛輪效應(yīng)”:
- 回歸的運(yùn)行,只有真正做到了無(wú)人值守,才有可能長(zhǎng)期高頻次運(yùn)行。
- 高頻次的運(yùn)行,可以充分暴露各種穩(wěn)定性問(wèn)題,提高回歸的穩(wěn)定性。
- 縮短執(zhí)行時(shí)間,一方面可以縮短“反饋弧”,加速各種穩(wěn)定性問(wèn)題的修復(fù),另一方面可以提高測(cè)試環(huán)境的“周轉(zhuǎn)率”,在不增加硬件成本的前提下實(shí)現(xiàn)更高頻次的回歸。
- 提高了穩(wěn)定性,可以縮短用于分析回歸結(jié)果的時(shí)間。如果一個(gè)有5,000個(gè)用例的回歸用例集只有90%的通過(guò)率,那每次跑完回歸有500個(gè)失敗的用例需要分析 ? ??。但如果通過(guò)率有99%,那就只有50個(gè)用例需要分析了。
強(qiáng)大的回歸能力的背后需要的支撐能力是:
- 優(yōu)質(zhì)的測(cè)試環(huán)境:要在預(yù)算允許的范圍內(nèi),確保測(cè)試環(huán)境的穩(wěn)定和資源充沛,這樣才能支撐起回歸的穩(wěn)定性和高頻次執(zhí)行。
- 配置代碼化(configuration-as-code)的能力。今天常見(jiàn)的web-based centralized配置變更管理模式不足以支持高頻詞、高并發(fā)的回歸運(yùn)行模式。實(shí)現(xiàn)了配置代碼化,才能實(shí)現(xiàn)快速的環(huán)境部署,以及在不同的環(huán)境之間用不同的配置跑回歸。配置代碼化并不是簡(jiǎn)單地把配置寫(xiě)在config文件里面,和代碼一起打包發(fā)布。配置代碼化是對(duì)這種config文件做法的否定之否定:配置可以在git里面修改;配置也可以在配置管理系統(tǒng)里面直接修改,變更會(huì)回沉到git里面。部署的時(shí)候,部署工具會(huì)把git里面的配置值以增量的方式推到配置管理系統(tǒng)里面。
把大項(xiàng)目拆成小項(xiàng)目做的能力
如前所述,把代碼拆成小塊分多次發(fā)布,的確是會(huì)增加開(kāi)發(fā)的工作量的。有不少開(kāi)發(fā)同學(xué)不理解為什么要這樣做。增加了這些工作量,能讓我們的研發(fā)模式更加敏捷。這個(gè)代價(jià)是值得付出的,這些額外的時(shí)間是值得花的。
大項(xiàng)目拆成小項(xiàng)目做的一些常見(jiàn)套路包括:
- 分兩部走:先向下兼容,再去掉兼容性。這就是前文舉的那個(gè)例子:X系統(tǒng)的API增加了一個(gè)新參數(shù),要求Y系統(tǒng)在調(diào)用這個(gè)API的時(shí)候必須要傳這個(gè)參數(shù)。拆成小項(xiàng)目的拆解方法是:首先,X的API新增的這個(gè)參數(shù)做成optional的,把X發(fā)布上線。然后等Y那邊的代碼改好發(fā)上線了以后,再把X的這個(gè)新參數(shù)改成required,再發(fā)布一次X。或者,也可以用一個(gè)feature flag來(lái)控制這個(gè)新參數(shù)是否required。
- Feature flag:有了feature flag,新功能的代碼寫(xiě)了一半也沒(méi)關(guān)系,可以把feature flag關(guān)掉,就算代碼發(fā)上線了也不會(huì)被執(zhí)行到。有時(shí)候,有些新功能所需要的代碼變更是改動(dòng)在老代碼里面的。這樣的代碼變更無(wú)法用feature flag來(lái)屏蔽。但這也沒(méi)關(guān)系,因?yàn)槲覀冇袕?qiáng)大的回歸能力,能盡我們所能確信這些的代碼變更至少不會(huì)break老功能、不會(huì)在發(fā)上線后造成故障。Anyway, 哪怕不是為了把大項(xiàng)目拆成小項(xiàng)目,feature flag也是需要的。Feature flag、白名單等都是很常見(jiàn)的continuous delivery手段。
- Capability probing:很多新功能涉及整條鏈路上各個(gè)系統(tǒng)的改造。現(xiàn)在往往上游系統(tǒng)的發(fā)布依賴于下游系統(tǒng)的發(fā)布。解耦這種依賴關(guān)系的一種方法是讓每個(gè)系統(tǒng)都通過(guò)一個(gè)統(tǒng)一的API接口來(lái)暴露自己當(dāng)前的能力。這樣,上游系統(tǒng)可以判斷下游系統(tǒng)當(dāng)前是否支持某個(gè)新功能所需要的能力Foo(例如,某種支付渠道),根據(jù)結(jié)果走不同的code path。
- 按域獨(dú)立發(fā)布也是一種很成熟的拆分的方法。按域獨(dú)立發(fā)布,實(shí)現(xiàn)域和域之間的解耦,能減少每次發(fā)布的系統(tǒng)的數(shù)量,降低發(fā)布風(fēng)險(xiǎn),增加發(fā)布的靈活度。
大項(xiàng)目拆成小項(xiàng)目,還需要有比較強(qiáng)的需求拆分的能力:能夠把一個(gè)全鏈路級(jí)別的需求文檔拆分成域級(jí)別、系統(tǒng)級(jí)別的需求,這樣每個(gè)域、每個(gè)系統(tǒng)可以“分而治之”。
?
Code Velocity和質(zhì)量、線上穩(wěn)定性的關(guān)系
從上面的分析可以看出來(lái),提高code velocity并不是以犧牲質(zhì)量為代價(jià)的。上面這些提高code velocity的手段,并沒(méi)有cut corner,并沒(méi)有降低質(zhì)量標(biāo)準(zhǔn),并沒(méi)有比今天少執(zhí)行任何測(cè)試。即便是頻繁的把代碼合回master,即便是把大項(xiàng)目拆成小項(xiàng)目做,該運(yùn)行的各種驗(yàn)證和測(cè)試還是繼續(xù)運(yùn)行。而且,為了要提高code velocity,實(shí)行了代碼門(mén)禁,建設(shè)了強(qiáng)大的跑回歸的能力,反而是對(duì)質(zhì)量有提高作用的。
提高code velocity也并不會(huì)降低線上穩(wěn)定性。把大項(xiàng)目拆成小項(xiàng)目做、更加頻繁的發(fā)布小塊代碼,能夠降低單次發(fā)布的風(fēng)險(xiǎn);發(fā)布中如果出了問(wèn)題,因?yàn)閜ayload小,排查和回滾也更方便。另外,在投入資源提高code velocity的同時(shí),我們不會(huì)降低對(duì)故障發(fā)現(xiàn)能力、止血能力、應(yīng)急能力、監(jiān)控核對(duì)等能力的投入。提高code velocity不會(huì)導(dǎo)致線上技術(shù)風(fēng)險(xiǎn)防控體系變?nèi)酢?/p>
將來(lái)
如果一個(gè)團(tuán)隊(duì)的“能頻繁的把代碼合回master”的能力做得足夠好了,就可以完全拋棄項(xiàng)目分支和迭代分支,每一個(gè)commit都直接checkin進(jìn)master,而且master分支每天都有若干個(gè)可以發(fā)布的版本???,每個(gè)版本都可以用一個(gè)不同的release分支來(lái)保存。這就是所謂的“主干開(kāi)發(fā)、分支發(fā)布”(Trunk-based Development)模式了。
到那時(shí)候,就有做到“天天發(fā)”的能力了。那時(shí)候,代碼從commit到上線可能平均只需要兩三天時(shí)間。那時(shí)候,因?yàn)橛辛恕疤焯彀l(fā)”的能力,甚至連緊急發(fā)布都不怎么需要了。
如果你希望加入螞蟻金服國(guó)際事業(yè)群,可以隨時(shí)與我們直接聯(lián)系。Java開(kāi)發(fā)、測(cè)試開(kāi)發(fā)、SRE工程師和工具開(kāi)發(fā)等崗位虛位以待,有興趣的童鞋可發(fā)簡(jiǎn)歷至:
intl_hire_account@service.alipay.com
【注】
1.一般會(huì)有兩個(gè)為期四周的迭代并行,每個(gè)迭代有自己的目標(biāo)發(fā)布窗口。發(fā)布窗口一般是每?jī)芍芤淮巍?/p>
2.“分支開(kāi)發(fā)、主干發(fā)布”的開(kāi)發(fā)模式來(lái)自于A successful Git branching model。但這種模式在實(shí)踐中是有不少問(wèn)題的(參見(jiàn)A succesful Git branching model considered harmful)。更好的模式是“主干開(kāi)發(fā)、分支發(fā)布”(aka. Trunk-based Development)
3.主分支可以是master,也可以是項(xiàng)目分支或者迭代分支。
4.單元測(cè)試和接口測(cè)試看代碼覆蓋率,回歸測(cè)試看業(yè)務(wù)覆蓋率。這在行業(yè)內(nèi)的一部分開(kāi)發(fā)和測(cè)試之間已經(jīng)形成共識(shí)了。
5.當(dāng)然,我們可以用技術(shù)的手段使得分析500個(gè)失敗的用例變得更容易。但這并不應(yīng)該成為我們不去提高通過(guò)率的理由。
6.版本:對(duì)于“大庫(kù)模式”(monolithic repo)來(lái)說(shuō)就是一個(gè)commit,對(duì)于“小庫(kù)模式”來(lái)說(shuō)就是每個(gè)repo的一個(gè)commit構(gòu)成的一個(gè)“截面”。
?
每天一篇技術(shù)文章,
看不過(guò)癮?
關(guān)注“阿里巴巴機(jī)器智能”微信公眾號(hào)
發(fā)現(xiàn)更多AI干貨。
與50位技術(shù)專家面對(duì)面20年技術(shù)見(jiàn)證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的如何提高一个研发团队的“代码速度”?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 阿里大数据技术如何进化?资深技术专家带你
- 下一篇: 疫苗事件发生后,阿里工程师连夜做了一件小