前段時(shí)期我負(fù)責(zé)部門內(nèi)部主干開(kāi)發(fā)落地相關(guān)事宜,這個(gè)過(guò)程中,也真真切切的體會(huì)到了多人開(kāi)發(fā)過(guò)程中,面對(duì)特性分支管理中,大家遇到的一些困擾,尤其面對(duì)敏捷迭代的開(kāi)發(fā)方式,合并沖突,集成測(cè)試,代碼重用等方面,都與高效兩個(gè)字背離。當(dāng)然,我在推進(jìn)主干開(kāi)發(fā)過(guò)程中,也遇到了一些問(wèn)題和坎坷,在這里,集中的做一次分享。
1. 概述
主干開(kāi)發(fā),是指開(kāi)發(fā)人員直接向主干(習(xí)慣上主干分支通常為:trunk 或 master)提交 / 推送代碼。通常,開(kāi)發(fā)團(tuán)隊(duì)的成員 1 天至少 1 次地將代碼提交到主干分支。在到達(dá)發(fā)布條件時(shí),從主干拉出發(fā)布分支(通常為 release),用于發(fā)布。若發(fā)現(xiàn)缺陷,直接在主干上修復(fù),并根據(jù)需要 cherry pick 到對(duì)應(yīng)版本的發(fā)布分支。
優(yōu)點(diǎn):
- 分支模型簡(jiǎn)單高效,開(kāi)發(fā)人員易于掌握不容易出現(xiàn)錯(cuò)誤操作
- 避免了分支合并、沖突解決的困擾
- 隨時(shí)擁有可發(fā)布的版本
- 有利于持續(xù)集成和持續(xù)交付
缺點(diǎn):
- 基礎(chǔ)架構(gòu)要求高:合入到主干的代碼若質(zhì)量不過(guò)關(guān)將直接阻塞整個(gè)團(tuán)隊(duì)的開(kāi)發(fā)工作,因此需要高效的持續(xù)集成平臺(tái)進(jìn)行把關(guān);
- 自動(dòng)化測(cè)試要求高:需有完備單元測(cè)試代碼,確保在代碼合入主干前能在獲得快速和可靠的質(zhì)量反饋;
- 最好有代碼評(píng)審:若代碼質(zhì)量要求高,需要配套代碼評(píng)審(CR)機(jī)制,在代碼提交到主干時(shí),觸發(fā) CR,通過(guò) Peer Review 后才能正式合入;
- 最好有特性開(kāi)關(guān):主干開(kāi)發(fā)頻發(fā)合入主干的情況下,特性拆分得很小,可能是半成品特性,需要配套特性開(kāi)關(guān)(Feature Toggle),只有當(dāng)特性整體開(kāi)發(fā)完才通過(guò)灰度發(fā)布等手段逐步打開(kāi);
適用環(huán)境:
- 對(duì)迭代速度要求高,希望需求快速交付上線
- 基礎(chǔ)架構(gòu)強(qiáng),持續(xù)集成工具高效;
- 團(tuán)隊(duì)成員習(xí)慣 TDD(測(cè)試驅(qū)動(dòng)開(kāi)發(fā)),代碼自動(dòng)化測(cè)試覆蓋率高(至少增量代碼的自動(dòng)化測(cè)試覆蓋率高);
2. 整體架構(gòu)
2.1 衡量主干開(kāi)發(fā)的效果
可以通過(guò)執(zhí)行以下操作來(lái)衡量主干開(kāi)發(fā)的效果。
測(cè)試的因素衡量的指標(biāo)目標(biāo)應(yīng)用代碼庫(kù)中的活躍分支數(shù)。衡量應(yīng)用代碼庫(kù)版本控制系統(tǒng)中的活躍分支數(shù),讓所有團(tuán)隊(duì)都能看到此數(shù)字。然后跟蹤目標(biāo)狀態(tài)的增量式進(jìn)度。不超過(guò)三個(gè)活躍分支。代碼凍結(jié)期。衡量團(tuán)隊(duì)的代碼凍結(jié)數(shù)及凍結(jié)時(shí)長(zhǎng)。這些衡量指標(biāo)還可以對(duì)合并沖突、代碼凍結(jié)、穩(wěn)定等方面所耗費(fèi)的時(shí)間進(jìn)行分類。無(wú)人提交代碼時(shí),沒(méi)有代碼會(huì)被凍結(jié)。將分支合并到主干的頻率。衡量合并的每個(gè)分支的二進(jìn)制(是 / 否)值,或者衡量每天合并的分支的百分比。每天至少合并一次。查看審批代碼更改所需的時(shí)間。如果您異步執(zhí)行代碼審核,請(qǐng)衡量審批更改請(qǐng)求所需的平均時(shí)間,并特別關(guān)注所需時(shí)間大大超過(guò)平均值的請(qǐng)求。設(shè)法使代碼審核成為在開(kāi)發(fā)過(guò)程中執(zhí)行的同步活動(dòng)。
2.2 常見(jiàn)誤區(qū)
如需全面采用主干開(kāi)發(fā),需要避免以下常見(jiàn)障礙:
- 繁瑣的代碼審核流程。許多組織的代碼審核流程都較為繁瑣,需要多次審批才能將更改合并到主干中。如果代碼審核很費(fèi)力并且需要數(shù)小時(shí)或數(shù)天才能完成,開(kāi)發(fā)者會(huì)避免小批量工作,改為進(jìn)行大批量更改。因?yàn)榇笈看a審核十分復(fù)雜,因此審核人員的審核時(shí)間會(huì)延長(zhǎng),進(jìn)而造成惡性循環(huán)。
- 這樣的結(jié)果是開(kāi)發(fā)者避免使用合并請(qǐng)求,從而導(dǎo)致合并請(qǐng)求經(jīng)常受到冷落。由于很難通過(guò)檢查來(lái)推導(dǎo)大規(guī)模更改對(duì)系統(tǒng)的影響,因此審核人員很可能會(huì)忽略缺陷,主干開(kāi)發(fā)的優(yōu)勢(shì)也就減弱了。
- 異步執(zhí)行代碼審核。如果您的團(tuán)隊(duì)實(shí)行結(jié)對(duì)編程,那么該代碼已由第二個(gè)人審核。如果需要進(jìn)一步審核,則應(yīng)該同步執(zhí)行:開(kāi)發(fā)者準(zhǔn)備提交代碼時(shí),應(yīng)立即讓團(tuán)隊(duì)中的其他人員審核代碼。開(kāi)發(fā)者不應(yīng)該要求進(jìn)行異步審核,例如向工具提交請(qǐng)求,然后在等待審核時(shí)啟動(dòng)新任務(wù)。合并延遲時(shí)間越長(zhǎng),就越有可能發(fā)生合并沖突和相關(guān)問(wèn)題。如果執(zhí)行同步審核,需要團(tuán)隊(duì)同意優(yōu)先審核彼此的代碼而不是處理其他工作。
- 在提交代碼之前未運(yùn)行單元測(cè)試或者自動(dòng)化測(cè)試。為了確保主干保持工作狀態(tài),在提交前對(duì)代碼更改運(yùn)行測(cè)試非常重要。此操作可以在開(kāi)發(fā)者工作站完成,許多工具也提供針對(duì)本地更改遠(yuǎn)程運(yùn)行測(cè)試,然后在通過(guò)測(cè)試后自動(dòng)提交更改的功能。如果開(kāi)發(fā)者知道自己無(wú)需大量繁雜流程即可將代碼提交到主干,那么就會(huì)小批量更改代碼,這些更改易于理解、審核、測(cè)試,并且可以更快地遷移到生產(chǎn)環(huán)境。
2.3 改進(jìn)主干開(kāi)發(fā)的 Tip
- 小批量開(kāi)發(fā)。主干開(kāi)發(fā)最重要的推動(dòng)因素之一就是團(tuán)隊(duì)學(xué)習(xí)如何小批量開(kāi)發(fā)。這需要為開(kāi)發(fā)團(tuán)隊(duì)提供培訓(xùn)和組織支持。
- 執(zhí)行同步代碼審核。如前所述,轉(zhuǎn)換為同步代碼審核或至少確保開(kāi)發(fā)者優(yōu)先進(jìn)行代碼審核,有助于確保所做的更改不必等待數(shù)小時(shí)甚至數(shù)天即可合并到主干中。
- 執(zhí)行全面的單元測(cè)試和自動(dòng)化測(cè)試。確保您擁有全面而實(shí)用的自動(dòng)化單元測(cè)試套件,并在每次提交之前運(yùn)行這些測(cè)試工具。
- 快速構(gòu)建。構(gòu)建和測(cè)試過(guò)程應(yīng)在幾分鐘內(nèi)執(zhí)行。目標(biāo)是將測(cè)試環(huán)境的 jdos 部署串聯(lián)到整個(gè) CI 的自動(dòng)化流水線之中
2.4 主干開(kāi)發(fā)流程說(shuō)明

如圖所示,研發(fā)小伙伴基于 master 分支開(kāi)發(fā),當(dāng)每次 merge 時(shí),都會(huì)觸發(fā)流水線驗(yàn)證過(guò)程。在流水線驗(yàn)證中:
1.EOS 代碼掃描,該掃描會(huì)掃描代碼中不規(guī)范的情況,按照代碼規(guī)約不同,會(huì)有不同的級(jí)別,包括 WARNING, MAJOR, CRITICAL, BLOCKER 四種級(jí)別,基于不同級(jí)別可以設(shè)置攔截規(guī)則,如果代碼不符合設(shè)定的攔截規(guī)則,將不予 merge。
2. 代碼評(píng)審會(huì)觸發(fā)代碼評(píng)審邀請(qǐng),可以根據(jù)設(shè)定,邀請(qǐng)組內(nèi)研發(fā),leader,或者測(cè)試人員參與代碼評(píng)審,通過(guò)設(shè)定規(guī)則,如果代碼評(píng)審?fù)ㄟ^(guò),才允許 merge。
3. 現(xiàn)在自動(dòng)進(jìn)行 maven 打包和單元測(cè)試工作,如果單元測(cè)試不通過(guò),將不予 merge。
4. 流水線會(huì)自動(dòng)將單元測(cè)試通過(guò)的 jar 包發(fā)布到測(cè)試的 JOS 分組進(jìn)行部署,部署完成后自動(dòng)調(diào)取線上自動(dòng)化測(cè)試流程,只有所有接口通過(guò)自動(dòng)化測(cè)試,才允許 merge。
3. 落地方案
3.1 單元測(cè)試
應(yīng)用架構(gòu): 基于 spring boot junit 編寫單元測(cè)試。
我們可以將測(cè)試按照模塊劃分,放在不同的目錄之下,可以分為集成測(cè)試和單元測(cè)試。這樣做的原因是,當(dāng)我們的項(xiàng)目邊的越來(lái)越大的時(shí)候,寫的測(cè)試會(huì)越來(lái)越多,當(dāng)所有測(cè)試都放在一個(gè)目錄下的時(shí)候,跑一次集成測(cè)試時(shí)間會(huì)很長(zhǎng)。具體目錄如下:
主開(kāi)發(fā)目錄測(cè)試開(kāi)發(fā)目錄測(cè)試模塊描述測(cè)試父類命名src/test/init初始化測(cè)試模塊InitTestBaseunitservice 單元測(cè)試模塊UnitTestBase jpa持久化層測(cè)試模塊JPATestBase mvccontroller 層測(cè)試MvcTestBase
所有測(cè)試模塊集成對(duì)應(yīng)的父類,然后類名必須以對(duì)應(yīng)模塊 + Test 結(jié)尾,例如:ShipperStatisticUnitTest
- 引入單元測(cè)試依賴 spring-boot-starter-test
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2. 引入 surefile 插件
<plugin>
<groupId>org.Apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<skip>false</skip>
<includes>
<include>**/unit/*Test.JAVA</include>
</includes>
</configuration>
</plugin>
3. 配置測(cè)試配置文件路徑
<resources>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<!--①-->
<excludes>
<exclude>Application*.properties</exclude>
</excludes>
</testResource>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
<includes>
<include>application.properties</include>
<include>important.properties</include>
<include>application-${activatedProperties}.properties</include>
</includes>
</testResource>
</testResources>
3.2 JaCoCo 代碼覆蓋率掃描
該處可以先串聯(lián)到流水線中,但不做門禁設(shè)置,等后期確定標(biāo)準(zhǔn)后,再打開(kāi)門禁設(shè)置。
3.3 EOS 靜態(tài)代碼掃描
在主干開(kāi)發(fā)模式中,因?yàn)?EOS 靜態(tài)代碼掃描,配置代碼合并門禁,確保代碼編碼規(guī)范。配置如下圖所示例子:

3.4 代碼線上評(píng)審
代碼評(píng)審?fù)ㄟ^(guò) Coding 內(nèi)置的評(píng)審規(guī)則實(shí)現(xiàn),規(guī)則設(shè)定如下:
1. 評(píng)審分支為 master 分支,并在 push 時(shí)創(chuàng)建代碼評(píng)審,并阻塞代碼直接合入目標(biāo)分支。
2. 評(píng)審人需要達(dá)到兩人及以上通過(guò)后,才能觸發(fā)隨后的操作。
3. 不允許自評(píng)。
4. 不允許特定成員跳過(guò)自動(dòng)化檢查。

3.5 特性開(kāi)關(guān)
遇到此情況,一般是多版本同時(shí)開(kāi)發(fā)的情況
此處跟業(yè)務(wù)強(qiáng)相關(guān),需要具體問(wèn)題具體分析。不過(guò)保證如下幾個(gè)原則:
- 代碼開(kāi)發(fā)盡量保證高內(nèi)聚低耦合,保證類的封閉性,同時(shí)可以用常用設(shè)計(jì)模式,保證功能的業(yè)務(wù)代碼解耦,有利于特性區(qū)分。
- 個(gè)別情況,可以引入 ducc 配置中心,實(shí)現(xiàn)特性開(kāi)關(guān)。
3.6 流水線配置
在行云流水線中,主要包括如下幾個(gè)節(jié)點(diǎn):

其中有一個(gè)配置上有一個(gè)小坑就是下載代碼。為什么這么說(shuō)呢?
因?yàn)槲覀兞魉€的觸發(fā)條件是跟 coding 上的代碼評(píng)審配合使用,當(dāng)主干分支發(fā)生 merge 請(qǐng)求時(shí),這個(gè)時(shí)候會(huì)觸發(fā)流水線,但拉取的代碼并不能直接填寫主干分支名稱(如:master), 原因是當(dāng)前的 commit 快照并沒(méi)有合并到 master 上,而是處于等待狀態(tài),只有當(dāng)流水線通過(guò)后才會(huì)真正合并到主干分支,這是如果下載代碼里拉取的主干分支就是不包含當(dāng)前提交內(nèi)容的快照,并不能滿足我們的訴求。
那么,我們?cè)撊绾闻渲媚兀?/p>
好在 Webhook 中會(huì)帶一些系統(tǒng)內(nèi)置全局參數(shù),其中 globalParams.user.WEBHOOK_ATTR_COMMIT_ID,代表的就是當(dāng)前提交請(qǐng)求所包含的 commit 快照,所以,我們只需要配置這個(gè)全局參數(shù)即可,如圖所示:

配合如上配置,我們?cè)谟|發(fā)設(shè)置中,只需要設(shè)置 MR created/Updated 事件即可。如下圖所示:

4. 總結(jié)
于許多開(kāi)發(fā)者而言,主干開(kāi)發(fā)是一項(xiàng)重大變革,您很可能會(huì)遇到一些阻礙。許多開(kāi)發(fā)者根本無(wú)法想像如何采用這種方式工作。一項(xiàng)好的做法是找到曾采用這種方式工作的開(kāi)發(fā)者,讓他們指導(dǎo)其他開(kāi)發(fā)者。讓一些團(tuán)隊(duì)轉(zhuǎn)為采用主干開(kāi)發(fā)方式工作也很重要。實(shí)現(xiàn)此目標(biāo)的一種方法是將大量具有主干開(kāi)發(fā)經(jīng)驗(yàn)的開(kāi)發(fā)者召集到一起,這樣至少有一個(gè)團(tuán)隊(duì)遵循主干開(kāi)發(fā)做法。然后,如果您確信遵循此做法的團(tuán)隊(duì)發(fā)揮預(yù)期的作用,則可以將其他團(tuán)隊(duì)轉(zhuǎn)換為這種風(fēng)格。
當(dāng)然主干開(kāi)發(fā)也不是銀彈,他也會(huì)有一些自己的弊端,比如在處理需求變更,或者同時(shí)多版本并行開(kāi)發(fā)時(shí),也需要建立多個(gè)臨時(shí)分支支持,想做到純粹的主干開(kāi)發(fā),也是過(guò)于理想化的結(jié)果。
作者:京東物流 趙勇萍
來(lái)源:京東云開(kāi)發(fā)者社區(qū)






