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