搭建iOS代碼質(zhì)量監(jiān)控平臺 - SonarQube

背景

近期團(tuán)隊(duì)內(nèi)進(jìn)行了一次項(xiàng)目代碼警告清理工作,對項(xiàng)目里各種觸發(fā)警告的代碼進(jìn)行優(yōu)化。人工改費(fèi)時(shí)費(fèi)力,效率低,同時(shí)也沒有合適的清理警告的自動化工具,因此只是清理了一部分便告一段落。事后對這次工作進(jìn)行總結(jié),提出下面三個(gè)問題:

  1. 代碼警告對我們有什么影響?
  2. 為什么會有這么多的警告代碼?
  3. 如何避免后續(xù)開發(fā)過程的代碼質(zhì)量問題?

第一個(gè)問題,喵神有句話這里引用一下

一個(gè)有節(jié)操的程序員會在乎自己的代碼的警告,就像在乎飯碗邊上有只死蟑螂那樣。 ——@onevcat

代碼警告實(shí)際帶來的風(fēng)險(xiǎn)具體有:代碼可讀性差、難以維護(hù)、Crash、邏輯錯(cuò)誤等,同時(shí)也是一個(gè)團(tuán)隊(duì)開發(fā)人員技術(shù)水平的體現(xiàn)。

代碼警告意味著編譯器發(fā)現(xiàn)某段代碼有風(fēng)險(xiǎn),雖然可能對實(shí)際邏輯功能不會產(chǎn)生影響,但是對嚴(yán)謹(jǐn)?shù)某绦騿T來說,始終有這樣一個(gè)、甚至上萬個(gè)警告提示在那里不去管,或者簡單的關(guān)閉警告,是不應(yīng)該的。

第二個(gè)問題,歸其原因,可以分為兩個(gè)方面:一方面是之前項(xiàng)目將警告關(guān)閉了,我們看不到大部分的代碼警告;另一方面是我們?nèi)鄙俅a質(zhì)量分析的機(jī)制,僅僅通過人工CodeReview的方式進(jìn)行代碼審查,能發(fā)現(xiàn)的問題是很有限的,缺少一些工具的幫助。

第三個(gè)問題,重點(diǎn)是需要建立起代碼質(zhì)量監(jiān)控的機(jī)制,通過一些自動化的工具,完成代碼質(zhì)量的分析、匯總。同時(shí)要把提升代碼質(zhì)量作為一個(gè)開發(fā)團(tuán)隊(duì)的日常工作任務(wù),定期的根據(jù)分析結(jié)果,解決相關(guān)的問題,清理飯碗邊上的蟑螂。

鑒于上述總結(jié),調(diào)研了一些工具后,發(fā)現(xiàn)業(yè)內(nèi)常用的的比較完善的代碼質(zhì)量監(jiān)控平臺是SonarQube。同時(shí)我們的測試團(tuán)隊(duì)也已經(jīng)在使用SonarQube對公司內(nèi)的項(xiàng)目代碼進(jìn)行檢測,由于iOS的工程支持需要一些配置,還沒有跑通,所以我們配合測試團(tuán)隊(duì)完成iOS項(xiàng)目支持SonarQube掃描,下面進(jìn)行相關(guān)工作的介紹。

工具介紹

SonarQube

SonarQube是一個(gè)代碼質(zhì)量監(jiān)控平臺,能夠匯總各類代碼分析工具的檢測報(bào)告,從Bug、問題代碼重復(fù)代碼、單測覆蓋度、技術(shù)債等維度展示項(xiàng)目代碼的健康程度,綜合各個(gè)維度的評價(jià),為代碼質(zhì)量進(jìn)行評級。

SonarQube有社區(qū)版和多種付費(fèi)版本,主要差別是對語言種類的支持有差異。SonarQube同時(shí)也支持插件開發(fā),通過插件的方式可以擴(kuò)展對語言的支持,所以可以使用社區(qū)版+插件的方式支持ObjC、Swift的質(zhì)量檢測,也是下面介紹的Sonar-Swift的實(shí)現(xiàn)方式。

Sonar-Swift

Sonar-Swift是一個(gè)面向ObjCSwift的開源靜態(tài)代碼分析工具集,通過SonarScannerSwiftLint、OCLint、TailorLizard等代碼分析工具的結(jié)果提交給SonarQube,完成iOS項(xiàng)目的代碼質(zhì)量監(jiān)控。對于ObjC的分析,由于OCLint存在很多問題,國內(nèi)有團(tuán)隊(duì)在Sonar-Swift的基礎(chǔ)上進(jìn)行二次開發(fā),引入了Infer掃描工具,來替代OCLint

SwiftLint

SwiftLint是用于進(jìn)行Swift靜態(tài)代碼分析的工具,通過HookClang獲取代碼的AST數(shù)據(jù),進(jìn)行分析后,輸出報(bào)告,同時(shí)使用SourceKit,將提示信息展示在Xcode編輯器內(nèi)。SwiftLint支持自定義檢測規(guī)則,這一點(diǎn)為制定適用于自己團(tuán)隊(duì)的開發(fā)規(guī)則比較友好。

OCLint

OCLint是用于進(jìn)行ObjC靜態(tài)代碼分析的工具,也是基于Clang提供的工具,獲取AST數(shù)據(jù)進(jìn)行分析,輸出報(bào)告。主要工作流程如下:

  1. xcodebuild或者xcrun生成構(gòu)建日志
  2. 結(jié)合xcpretty,輸出一個(gè)符合JSONCompilationDatabase標(biāo)準(zhǔn)的json文件,文件包含的主要是此次構(gòu)建的每個(gè)源碼文件、編譯命令和文件路徑文件路徑。
  3. OCLint根據(jù)該文件進(jìn)行二次編譯,在二次編譯過程中,獲取AST數(shù)據(jù)進(jìn)行分析。

OCLint也支持自定義檢測規(guī)則,但是在大型項(xiàng)目的實(shí)際接入過程中,問題較多,個(gè)別文件編譯失敗時(shí)導(dǎo)致整體工作流程失敗,使用不夠友好。

Infer

Infer也是可以用于進(jìn)行ObjC靜態(tài)代碼分析的工具,由FaceBook發(fā)布,開源。工作流程與OCLint類似,也是根據(jù)構(gòu)建日志進(jìn)行二次編譯,輸出分析報(bào)告。比較好的一點(diǎn)是即使個(gè)別文件編譯失敗,Infer仍然可以繼續(xù)執(zhí)行,不會打斷整體工作流程失敗,也是我們選擇Infer的一個(gè)主要原因。

SonarScanner

SonarScannerSonarQube提供的一個(gè)工具,能夠根據(jù)配置文件將指定工程及掃描報(bào)告上傳到SonarQube平臺中??梢岳斫鉃槭且粋€(gè)數(shù)據(jù)收集器,收集、上報(bào)代碼分析的數(shù)據(jù)。

工作流程

Sonar-Swift
Sonar-Swift的工作流程如上圖所示,分為以下幾個(gè)步驟:

  • xcodebuild構(gòu)建工程,輸出xcodebuild.log
  • xcpretty處理xcodebuild.log,輸出compile_commands.json
  • infer處理compile_commands.json,輸出report.json
  • swiftlint處理swift文件,輸出swiftlint.txt
  • lizard處理ObjC文件,輸出lizard-report.xml
  • Sonar-Scanner收集report.json、swiftlint.txt、lizard-report.xml上報(bào)到SonarQube

搭建過程

上面介紹了相關(guān)工具及整體的工作流程,接下來具體記錄下在搭建平臺的步驟及相關(guān)配置。

SonarQube服務(wù)

首先我們需要搭建起SonarQube服務(wù),官網(wǎng)提供多種搭建方式,我們選擇使用Docker一鍵安裝,省心省力。兼容性上需要注意兩點(diǎn):

  • SonarQube目前不支持M1的芯片設(shè)備,Docker部署失敗
  • SonarQube 9.x版本不支持Sonar-Swift插件,服務(wù)啟動失敗

鑒于上述兩個(gè)問題,我們使用的是基于Intel芯片的Mac設(shè)備,SonarQube的版本是8.9.2-communitydocker鏡像傳送門。

docker pull sonarqube:8.9.2-community

部署成功后,需要安裝Sonar-Swift插件,下載插件jar包,復(fù)制到docker上sonarqube的服務(wù)目錄下:

docker cp tal-sonar-swift-plugin-1.5.0.jar sonarqube:/opt/sonarqube/extensions/plugins/

啟動sonarqube服務(wù),默認(rèn)的端口是9000,本地打開localhost:9000即可看到SonarQube的頁面,默認(rèn)賬號密碼都是admin,首次登錄后會提示更新密碼。

服務(wù)搭建好后,在平臺上創(chuàng)建一個(gè)工程,支持通過GitLab等方式,我們選擇通過手工的方式創(chuàng)建,完成提示步驟后即可創(chuàng)建工程。工程創(chuàng)建好后,可以進(jìn)行一些自定義的設(shè)置,根據(jù)項(xiàng)目的實(shí)際需要,過濾不在監(jiān)控范圍內(nèi)的目錄、選擇Quality Profiles,在Quality Profiles里,可以看到infer的選擇,設(shè)置為默認(rèn)選項(xiàng)即可。

參考配置

配置路徑時(shí)有兩個(gè)地方需要注意下:

  • 使用正則表達(dá)式來過濾,如果實(shí)際使用過程中有一些文件被忽略掉了,可以驗(yàn)證下正則表達(dá)式是否正確。
  • 在確認(rèn)正則沒問題的情況下,還是有文件沒有上報(bào)上來,可以再確認(rèn)下想要上報(bào)分析的文件是不是在項(xiàng)目的.gitignore列表里,SonarScanner在掃描時(shí)會將.gitignore忽略的文件一起忽略掉。

對于.gitignore忽略的文件,可以通過設(shè)置sonar.scm.exclusions.disabled=true來關(guān)閉,具體設(shè)置的地方可以在SonarQube的后臺,也可以在Sonar-Swift提供的sonar-project.properties文件中配置。

SonarScanner安裝

下載SonarScanner,在全局環(huán)境變量中設(shè)置執(zhí)行路徑,以zsh為例,在~/.zshrc中增加以下代碼:

export SONAR_SCANNER_PATH=your scanner bin path
export PATH=$SONAR_SCANNER_PATH:$PATH

執(zhí)行source ~/.zshrc后,命令行輸入sonar-scanner確認(rèn)執(zhí)行路徑是否配置成功。

sonar-scanner支持通過sonar-project.properties文件的方式配置相關(guān)屬性,該文件內(nèi)可以配置sonarqube服務(wù)地址、工程名稱(SonarQube上對應(yīng)的工程信息)、登錄名、密碼、登錄token等。Sonar-Swift也提供了一個(gè)默認(rèn)的模版,我們可以將其放到工程目錄里,執(zhí)行sonar-scanner命令時(shí),會讀取該配置,根據(jù)配置內(nèi)容上報(bào)數(shù)據(jù)。

SwiftLint安裝配置

SwiftLint的安裝可以參考其Github指導(dǎo)進(jìn)行安裝即可。需要指出的一點(diǎn)是SwiftLint支持自定義規(guī)則,自定義的規(guī)則通過.swiftlin.yml文件來設(shè)置,所以在后面使用Sonar-Swift提供的腳本時(shí),可以在腳本內(nèi)進(jìn)行修改,使用自己提供的配置文件目錄進(jìn)行檢查。

Infer安裝配置

Infer的安裝也可以參考其官網(wǎng)進(jìn)行安裝即可。Infer執(zhí)行時(shí)的一些命令行選項(xiàng)配置可以通過在執(zhí)行目錄下設(shè)置.inferconfig文件來配置。Infer并沒有在Sonar-Swift提供的腳本中設(shè)置,所以在執(zhí)行Sonar-Swiftrun-sonar-swift.sh腳本中,需要增加調(diào)用infer的相關(guān)命令。

if [ "$infer" = "on" ]; then
        runCommand /dev/stdout infer run --keep-going
fi

同時(shí)在sonar-project.properties文件中,需要指定infer的報(bào)告路徑

sonar.swift.infer.report=infer-out/report.json
Sonar-Swift配置&腳本

Sonar-Swift除了提供了一個(gè)jar包插件外,還提供了一個(gè)sonar-project.properties配置模版、一個(gè)run-sonar-swift.sh腳本。在具體接入時(shí),可以根據(jù)自己項(xiàng)目的實(shí)際需要,對這兩個(gè)文件進(jìn)行修改。其中run-sonar-swift.sh的腳本主要是將工作流程圖中介紹的過程整合起來,方便執(zhí)行。

run-sonar-swift.sh

總結(jié)

本次著重介紹了Sonar-Swift的整體搭建過程,一些細(xì)節(jié)問題沒有完全列出,還需要在實(shí)踐過程中,具體項(xiàng)目具體分析。建立起質(zhì)量監(jiān)控平臺不是最終目的,保證代碼質(zhì)量才是我們的目標(biāo)。所以后續(xù)還需要將這個(gè)平臺真正的使用起來,在項(xiàng)目的迭代過程中,將平臺的分析數(shù)據(jù)作為監(jiān)控指標(biāo),根據(jù)分析結(jié)果不斷的修復(fù)相關(guān)問題,以此提升我們的代碼質(zhì)量,同時(shí)在解決問題的過程中,也能獲得技術(shù)上的成長。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容