提前在開發(fā)階段暴露代碼問題,攜程Alchemy代碼質(zhì)量平臺(tái)
一、背景
隨著敏捷開發(fā),DevOps開發(fā)模式的流行,代碼質(zhì)量分析作為研發(fā)質(zhì)量保證體系的重要組成部分,不僅能有效的降低因頻繁迭代帶來的故障風(fēng)險(xiǎn),而且對(duì)整個(gè)工程團(tuán)隊(duì)的效能提升有著巨大的價(jià)值。
攜程很久以前就已經(jīng)開始進(jìn)行DevOps的建設(shè),通過Gitlab CI/CD在開發(fā)提交代碼觸發(fā)的流水線pipeline中引入靜態(tài)掃描、單元測(cè)試、集成測(cè)試等流程,在開發(fā)過程中打造了一套閉環(huán)的代碼質(zhì)量保障體系。其中,在靜態(tài)代碼分析階段引入了SonarQube,并且通過對(duì)原有SonarQube代碼規(guī)范庫中的規(guī)范進(jìn)行篩選和擴(kuò)展,形成了自己的代碼規(guī)范庫。但是在實(shí)際應(yīng)用過程中,我們發(fā)現(xiàn)仍然有一些問題需要優(yōu)化解決:
- 在開發(fā)過程中,代碼規(guī)范只能通過開發(fā)人員自我約束,缺少統(tǒng)一的平臺(tái)對(duì)各應(yīng)用代碼的潛在風(fēng)險(xiǎn)問題統(tǒng)一進(jìn)行分析,且問題難以定位到開發(fā)人員進(jìn)行治理。
- 代碼單元測(cè)試通過率和代碼覆蓋率都很高,但仍然存在一些在單元測(cè)試階段應(yīng)被發(fā)現(xiàn)的問題未暴露出來,導(dǎo)致上線后出現(xiàn)bug,單元測(cè)試用例的質(zhì)量缺乏有效性及可靠性保證。
- 隨著項(xiàng)目的發(fā)展,開發(fā)人員為了避免影響已有功能,在開發(fā)過程中大量復(fù)制粘貼,導(dǎo)致出現(xiàn)很多難以維護(hù)的重復(fù)代碼,且程序邏輯結(jié)構(gòu)過于復(fù)雜,修改邏輯牽一發(fā)而動(dòng)全身,可維護(hù)性差。
- 代碼中充斥著大量的sql拼接,以及一些不規(guī)范的寫法導(dǎo)致潛在的問題,需要對(duì)此類代碼進(jìn)行治理。
二、平臺(tái)介紹
Alchemy平臺(tái)是一個(gè)代碼質(zhì)量分析平臺(tái),提供Infer分析、代碼分析、自定義掃描、代碼搜索等功能,其中代碼質(zhì)量分析內(nèi)容包含代碼行,sonar問題, infer問題,UT規(guī)則,重復(fù)代碼以及圈復(fù)雜度等。用戶可以根據(jù)自己的需求在平臺(tái)上進(jìn)行掃描項(xiàng)配置,并查看應(yīng)用的代碼質(zhì)量分析結(jié)果。
為了及時(shí)獲得對(duì)提交代碼變更的質(zhì)量反饋,作為DevOps中重要的一環(huán),Alchemy平臺(tái)與Gitlab CI/CD相結(jié)合,將靜態(tài)代碼分析提前至開發(fā)提交或合并代碼階段。開發(fā)人員提交代碼至Gitlab,觸發(fā)流水線相關(guān)任務(wù)執(zhí)行,任務(wù)執(zhí)行完成之后可以對(duì)某些指標(biāo)(如增量代碼引入的空指針)設(shè)置紅線進(jìn)行卡點(diǎn),如果指標(biāo)在指定范圍內(nèi),允許合并代碼并發(fā)布,如果指標(biāo)超過了紅線設(shè)置范圍,則不允許合并代碼,開發(fā)人員修復(fù)問題后再次提交代碼進(jìn)行流水線的集成發(fā)布。掃描分析結(jié)果可以在Gitlab或者Alchemy平臺(tái)上展示,幫助開發(fā)人員在快速迭代的同時(shí)保證代碼質(zhì)量。

靜態(tài)代碼掃描流程
三、系統(tǒng)架構(gòu)
Alchemy平臺(tái)包含Alchemy-client、Alchemy-service和Alchemy-web。其中,Alchemy-client為掃描腳本,包含Infer分析,UT掃描,重復(fù)代碼掃描、自定義掃描等功能,集成到Docker鏡像中,Alchemy-service提供數(shù)據(jù)存儲(chǔ)、分析等后臺(tái)服務(wù),且依賴代碼搜索服務(wù)CodeSearch-Service實(shí)現(xiàn)代碼搜索功能,Alchemy-web負(fù)責(zé)頁面交互。
開發(fā)人員提交代碼,觸發(fā)Gitlab CI/CD中靜態(tài)代碼分析job在GitRunner中執(zhí)行,執(zhí)行時(shí)先從Docker倉庫下載鏡像,啟動(dòng)容器后執(zhí)行Alchemy-client腳本,腳本會(huì)根據(jù)平臺(tái)配置來執(zhí)行相應(yīng)的掃描任務(wù),掃描完成后,將結(jié)果上傳至Alchemy-service,存儲(chǔ)到mongodb數(shù)據(jù)庫,最終在前端頁面展示分析結(jié)果。

Alchemy架構(gòu)圖
四、功能
4.1 Infer分析
Infer是Facebook出品的一個(gè)靜態(tài)分析工具,可以分析Java、Objective-c或者C代碼,報(bào)告潛在的問題,包括空指針,資源泄漏等。Alchemy平臺(tái)將Infer引入代碼靜態(tài)分析階段,目前已支持全量和增量分析兩種模式。
全量模式需分析應(yīng)用倉庫中的所有代碼,能分析出所有代碼引入的潛在問題,對(duì)于代碼量較大的應(yīng)用,由于需要分析所有代碼文件,掃描時(shí)間比較長(zhǎng),在一定程度上影響開發(fā)發(fā)布進(jìn)度,且對(duì)未修改的代碼進(jìn)行了非必要的重復(fù)分析,在代碼修改量較少的情況下造成資源浪費(fèi)。因此,我們嘗試加入緩存機(jī)制,并引入了增量分析模式,增量模式需要獲取本次提交修改的文件,在分析階段只針對(duì)這些改動(dòng)文件進(jìn)行分析,能大大節(jié)省分析時(shí)間。Infer分析流程如下:

Infer分析流程圖
在分析過程中,首先判斷是否為第一次分析,如果沒有分析歷史記錄,則系統(tǒng)默認(rèn)采用全量模式,否則需判斷Infer掃描配置,若配置為全量模式,則分析此代碼工程的全部文件,若配置為增量模式,需獲取此次提交修改的文件列表,編譯過程完成之后,在分析階段指定文件列表進(jìn)行分析。獲取到分析出的問題列表后,判斷問題所在的行是否為修改行,如果是,則記錄為本次修改導(dǎo)致的新增問題,否則為歷史遺留的全量問題。
在實(shí)際應(yīng)用中,針對(duì)封裝的判空方法,通過添加@TrueOnNull或@FalseOnNull注解,可識(shí)別對(duì)象的判空操作。但對(duì)于第三方包的判空方法,如CollectionUtils.isEmpty(), 由于未添加注解,即使添加判空方法,仍會(huì)被誤識(shí)別為空引用。因此,Alchemy平臺(tái)加入了忽略操作,針對(duì)此類問題進(jìn)行二次確認(rèn),避免重復(fù)誤判。

Infer誤判結(jié)果
4.2 UT規(guī)則掃描
單元測(cè)試是DevOps流程中一個(gè)非常重要的環(huán)節(jié),我們可以利用通過率和代碼覆蓋率等指標(biāo)來衡量單元測(cè)試用例的完整程度,卻很難保證用例的有效性。阿里巴巴java開發(fā)手冊(cè)規(guī)定,單元測(cè)試不允許使用System.out來進(jìn)行人肉驗(yàn)證,必須使用斷言assert來驗(yàn)證。
在實(shí)際的開發(fā)過程中,開發(fā)人員把主要的時(shí)間用在寫業(yè)務(wù)邏輯代碼上,在編寫單元測(cè)試用例時(shí),往往容易忽略對(duì)結(jié)果的驗(yàn)證,雖然通過率和代碼覆蓋率很高,但上線后仍然出現(xiàn)未對(duì)接口結(jié)果進(jìn)行驗(yàn)證而導(dǎo)致嚴(yán)重問題的情況。無效的單元測(cè)試用例包含以下幾種:
- 空函數(shù):函數(shù)體為空;
- 空斷言:用例中實(shí)現(xiàn)了對(duì)被測(cè)接口的調(diào)用邏輯,但未對(duì)接口返回結(jié)果進(jìn)行驗(yàn)證;
- 偽斷言:用例中使用類似assertTrue(True)的假斷言。
通過掃描空斷言、空函數(shù)、偽斷言等問題,能判斷該用例是否對(duì)代碼邏輯進(jìn)行必要的驗(yàn)證。Alchemy平臺(tái)支持單元測(cè)試用例的有效性驗(yàn)證,目前,平臺(tái)支持Java、Kotlin、Groovy和Nodejs,同時(shí)也支持全量和增量2種掃描結(jié)果,全量結(jié)果即為所有測(cè)試用例中不滿足規(guī)則的用例,增量結(jié)果為本次提交修改的測(cè)試用例中不滿足規(guī)則的用例。

UT掃描流程
對(duì)單個(gè)單元測(cè)試文件的掃描流程如圖。首先根據(jù)文件后綴判斷語言類型,然后根據(jù)不同語言類型規(guī)則獲取該文件中的用例信息,包含case名稱、起止行、作者、最近修改時(shí)間、函數(shù)內(nèi)容等,針對(duì)函數(shù)內(nèi)容,先判斷用例是否有斷言,如果有,則判斷是否為偽斷言,如果未斷言或者被判為偽斷言,還需進(jìn)一步根據(jù)用例的起止行,結(jié)合本次提交的改動(dòng)信息,分析該用例是否為增量改動(dòng)的用例,如果是則標(biāo)記為增量問題,最后將結(jié)果推送給gitlab,在合并代碼或發(fā)布時(shí)根據(jù)紅線配置進(jìn)行卡點(diǎn)。掃描結(jié)果如圖所示。

UT掃描結(jié)果
4.3 重復(fù)代碼掃描
重復(fù)代碼即為重復(fù)或近似的代碼,在開發(fā)過程中,開發(fā)人員為了避免影響現(xiàn)有功能,使用復(fù)制粘貼快速完成開發(fā)任務(wù),導(dǎo)致出現(xiàn)大量的重復(fù)代碼。重復(fù)代碼不僅讓代碼量大增,造成編譯速度慢,而且占用大量存儲(chǔ)空間,如果想要修改其中一段代碼邏輯,則需要同時(shí)修改多個(gè)地方,容易遺漏,可維護(hù)性差。
當(dāng)前市面上有很多代碼檢測(cè)工具,如Simian,PMD-CPD,CloneDR等,由于在實(shí)現(xiàn)算法上有所不同,不同工具所能檢測(cè)的重復(fù)代碼類型也不盡相同。我們利用PMD-CPD掃描代碼倉庫,可以檢測(cè)出單文件或多個(gè)文件中除了空格、注釋、換行以及變量名以外內(nèi)容完全一致的代碼段信息,這些信息包含文件路徑、代碼段內(nèi)容、起止行以及作者信息,詳情結(jié)果如圖所示。

重復(fù)代碼詳情結(jié)果
4.4 自定義規(guī)則掃描
Alchemy支持對(duì)自定義規(guī)則的掃描,通過配置自定義正則表達(dá)式和掃描范圍,識(shí)別代碼文件中滿足配置規(guī)則的代碼段,可用于掃描代碼中的拼接SQL,敏感詞等,并且可將不合規(guī)的代碼定位到相關(guān)開發(fā)人員。

自定義掃描流程
單個(gè)文件掃描流程如圖,首先判斷文件是否在掃描范圍內(nèi),若不在則直接跳轉(zhuǎn)掃描下一個(gè)文件,否則讀取文件內(nèi)容,同時(shí)根據(jù)文件類型獲取對(duì)應(yīng)的自定義規(guī)則,匹配滿足規(guī)則的代碼段信息,包含代碼段內(nèi)容、嚴(yán)重程度、起止行、作者等。在某些場(chǎng)景下,需要設(shè)置子規(guī)則進(jìn)行二次匹配,比如掃描update未指定where條件的sql語句,可先根據(jù)規(guī)則找到update語句,然后根據(jù)子規(guī)則判斷是否帶where條件,最終記錄二次匹配的結(jié)果。
4.5 代碼分析
使用不同工具統(tǒng)計(jì)的代碼質(zhì)量指標(biāo)可能分散在不同的平臺(tái),對(duì)這些指標(biāo)進(jìn)行全面分析的過程中難免會(huì)有所遺漏,特別是對(duì)于未設(shè)置發(fā)布卡點(diǎn)的指標(biāo),開發(fā)人員可能并不會(huì)關(guān)注它們,導(dǎo)致代碼存在大量的潛在問題未被分析治理。
Alchemy代碼分析模塊可以對(duì)代碼不同維度的指標(biāo)進(jìn)行統(tǒng)計(jì)分析,包括代碼行、單元測(cè)試、infer問題、Sonar問題、重復(fù)代碼、圈復(fù)雜度等。用戶可以在代碼分析頁面查看各維度問題分布情況,從而對(duì)項(xiàng)目的整體風(fēng)險(xiǎn)指標(biāo)進(jìn)行更全面的分析,可以根據(jù)問題的嚴(yán)重程度設(shè)置優(yōu)先級(jí)進(jìn)行針對(duì)性的治理。

代碼分析結(jié)果
4.6 代碼搜索
在開發(fā)過程中,對(duì)于一些公共操作如中間件的使用方式,開發(fā)人員可能需要四處尋找接入文檔。Alchemy提供代碼搜索功能,可以幫助開發(fā)人員根據(jù)關(guān)鍵詞來查找收錄項(xiàng)目中的代碼使用示例,用戶可以根據(jù)項(xiàng)目倉庫、代碼語言以及作者等條件進(jìn)行細(xì)分查詢。在編碼過程中,命名規(guī)范是一個(gè)容易被忽視的問題,使用Alchemy的變量命名功能,用戶可以根據(jù)不同語言,搜索中英文關(guān)鍵詞來獲取推薦的規(guī)范命名參考,能極大地提高開發(fā)效率。
?
代碼搜索結(jié)果
五、結(jié)束語
在本文中,我們介紹了Alchemy平臺(tái)提供的代碼靜態(tài)分析,代碼探索以及通過與Gitlab CI/CD集成帶來的持續(xù)集成能力,可以在開發(fā)階段暴露出更多的代碼潛在問題和風(fēng)險(xiǎn),并及時(shí)反饋給相關(guān)人員。目前攜程酒店已接入項(xiàng)目800+,且在開發(fā)提交代碼和發(fā)布階段將分析的潛在問題接入了卡點(diǎn)流程。在后續(xù)的工作中,我們將從以下幾個(gè)方向進(jìn)行進(jìn)一步的優(yōu)化:
- 在代碼分析層面支持更多語言;
- 開發(fā)IDE插件,在編碼階段實(shí)時(shí)掃描代碼;
- 繼續(xù)深挖代碼風(fēng)險(xiǎn)指標(biāo),并引入評(píng)估機(jī)制。






























