前言
??? 首次接觸代碼分析,源于公司對迭代規(guī)范流程的的試點扰肌。在迭代中抛寝,如何保證輸出的代碼是高質(zhì)量的,或者說是沒有明顯缺陷的曙旭,這種高質(zhì)如何量化盗舰,這就是接下來要介紹的: 靜態(tài)代碼分析。
代碼分析的意義
降低風險
??? 迭代中代碼掃描分析桂躏,成為上線發(fā)版不可或缺的一環(huán)钻趋,代碼分析能檢測出代碼中明顯存在的問題,降低風險剂习,比如:強制類型轉(zhuǎn)換蛮位、內(nèi)存泄漏、空指針……
提高代碼質(zhì)量
??? 代碼的質(zhì)量水平鳞绕,是直接影響系統(tǒng)穩(wěn)定程度的原因之一失仁,Code Review分為工具、人工兩種们何,通過Code Review萄焦,可以量化代碼質(zhì)量,是否達到發(fā)版的質(zhì)量標準垂蜗,這里重點講工具Code Review楷扬。
掃描環(huán)境搭建
1. 環(huán)境
- 版本控制工具:Git
- 源碼語言版本:Swift 5解幽、OC
- 開發(fā)工具:Xcode 11.3
- JDK:Jdk-1.8.0_221 JDK1.8地址
- 持續(xù)集成工具:Jenkins
- 質(zhì)量管理工具:SonarQube(7.8 )
2. SonarQube的工具鏈
- 源碼工程(Project):Swift、OC
-
MySQL數(shù)據(jù)庫(SonarQube Database):版本5.7.19-macos10.12-x86_64烘苹,存放配置信息和分析結(jié)果信息
創(chuàng)建用戶及數(shù)據(jù)庫躲株,mysql修改密碼自行操作,隨意12345678
cd /usr/local/mysql/
source ~/.bash_profile
mysql -u root -p
mysql> CREATE DATABASE sonar CHARACTER SET utf8 COLLATE utf8_general_ci;
mysql> CREATE USER 'sonar' IDENTIFIED BY 'sonar';
mysql> GRANT ALL ON sonar.* TO 'sonar'@'%' IDENTIFIED BY 'sonar';
mysql> GRANT ALL ON sonar.* TO 'sonar'@'localhost' IDENTIFIED BY 'sonar';
mysql> FLUSH PRIVILEGES;
mysql> quit;
- 服務器(SonarQube Server):iMac 镣衡,發(fā)布應用霜定,在線瀏覽、配置分析廊鸥;
- 客戶端(SonarQube Scanner):版本4.2.0.1873望浩,執(zhí)行源代碼分析。
注:Sonarqube-7.8 社區(qū)版 7.9版本以下支持MySQL(>=5.6<8)
3. 運行SonarQube
- 解壓sonarqube-7.8.zip
- 修改sonarqube-7.8中conf文件夾下的sonar.properties
sonar.jdbc.url=jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance&useSSL=false
# mysql中創(chuàng)建的sonarQube用戶
sonar.jdbc.username=root
# 創(chuàng)建用戶對應的密碼
sonar.jdbc.password=12345678
# 設置編碼格式為UTF-8
sonar.sorceEncoding=UTF-8
# sonar登陸用戶名
sonar.login=admin
# sonar登陸密碼
sonar.password=admin
sonar安裝成功
- 開啟SonarQube訪問外部訪問惰说,修改sonarqube-7.8中conf文件夾下的sonar.properties文件磨德,注釋掉sonar.web.context=
- 開啟apache httpd隨機啟動,提供外部訪問
sudo launchctl load -w /System/Library/LaunchDaemons/org.apache.httpd.plist
- Mac系統(tǒng)升級之后吆视,啟動Sonar服務部不會自送啟動MySQL典挑,導致Sonar服務啟動失敗,需要在開機啟動項中增加MySQL啟動配置啦吧。
sh /usr/local/mysql-5.7.19-macos10.12-x86_64/support-files/mysql.server start
- 配置sonar開機自啟您觉,在/Users/xxx/Document下創(chuàng)建Config文件夾,在Config下創(chuàng)建startup.sh文件授滓,添加以下腳本
# MySQL開機啟動
sh /usr/local/mysql-5.7.19-macos10.12-x86_64/support-files/mysql.server start
# Sonar開機啟動
sh /usr/local/sonarqube-7.8/bin/macosx-universal-64/sonar.sh start
# Jenkins開機自啟 并做外網(wǎng)端口映射
java -jar /usr/local/Cellar/jenkins/2.271/libexec/jenkins.war --httpPort=8080
-
將run sonar.sh文件添加到iMac登錄項中
image.png
自此SonarQube服務開機自啟動琳水、外部訪問配置完畢
4. 安裝插件
- sonar-swift-0.4.6
- sonar-l10n-zh-plugin-1.28
-
xcpretty-0.3.0 ((see instructions below) installed and
sudo gem install -n /usr/local/bin xcpretty
). -
SwiftLint-0.41.0 (HomeBrew installed and
brew install swiftlint
). Version 0.3.0 or above. -
Tailor-0.12.0_1 (HomeBrew installed and
brew install tailor
). Version 0.11.1 or above. -
slather-2.4.7 (
sudo gem install -n /usr/local/bin slather
). Version 2.1.0 or above (2.4 since Xcode 8.3). -
lizard (PIP installed and
sudo pip install lizard
) -
OCLint-0.13.1 installed or (HomeBrew installed and
brew tap oclint/formulae, brew install oclint
). Version 0.11.0 recommended (0.13.0 since Xcode 9). - Faux Pas command line tools installed (optional)
5. 插件配置
從已下載的sonar-swift-0.4.6中,拷貝backelite-sonar-swift-plugin-0.4.6.jar文件到sonarqube-7.8/extensions/plugins里
從已下載的漢化插件sonar-l10n-zh-plugin-1.28中般堆,拷貝sonar-l10n-zh-plugin-1.28文件到sonarqube-7.8/extensions/plugins里
下載run-sonar-swift.sh文夾拷貝到swift項目的根目錄下(與projext.xcodeproj 同級)
下載sonar-project.properties文件拷貝到swift項目的根目錄下(與projext.xcodeproj 同級)
- 編輯sonar-project.properties文件在孝,根據(jù)swift項目修改其中的參數(shù)
#
# Swift SonarQube Plugin - Enables analysis of Swift and Objective-C projects into SonarQube.
# Copyright ? 2015 Backelite (${email})
#
##########################
# Required configuration #
##########################
# 項目key, 與sonar中項目key一致
sonar.projectKey=xxx
# 項目 name
sonar.projectName=xxx
# 版本號
sonar.projectVersion=2.3.0
# 分支
sonar.branch.name=XXX
# Encoding of the source code
sonar.sourceEncoding=UTF-8
# 語言 ObjC / Swift
sonar.language=swift
# swift 源文件路徑
sonar.sources=Project/Clasess
# 過濾文件、目錄
sonar.test.inclusions=**/*Test*/**
sonar.test.inclusions=*.swift
sonar.exclusions=Project/Clasess/Vendors/**/*
# 項目描述
sonar.projectDescription=XXX ios with the SonarScanner
# 測試目錄的路徑
sonar.tests=ProjectTests,ProjectUITests
# 模擬器配置
sonar.swift.simulator=platform=iOS Simulator,name=iPhone X,OS=latest
# scheme Configuration Debug/Release
sonar.swift.appConfiguration=Debug
# Xcode 工程文件配置 xcodeproj郁妈、xcworkspace
sonar.swift.project=Project.xcodeproj
sonar.swift.workspace=Project.xcworkspace
# Xcode Scheme
sonar.swift.appScheme=Project
# App 名稱
sonar.swift.appName=Project
##########################
# Optional configuration #
##########################
# SCM
sonar.scm.enabled=true
# sonar.scm.url=scm:git:http://xxx
# JUnit report generated
sonar.junit.reportsPath=sonar-reports/
# Lizard report generated
sonar.swift.lizard.report=sonar-reports/lizard-report.xml
# Coverage report generated
sonar.swift.coverage.reportPattern=sonar-reports/coverage-swift*.xml
# OCLint report generated
sonar.objectivec.oclint.report=sonar-reports/*oclint.txt
# Swiftlint report generated
sonar.swift.swiftlint.report=sonar-reports/*swiftlint.txt
# Paths to exclude from coverage report (surefire, 3rd party libraries etc.)
# sonar.swift.excludedPathsFromCoverage=pattern1,pattern2
sonar.swift.excludedPathsFromCoverage=.*Tests.*
# Tailor report generated
sonar.swift.tailor.config=--no-color --max-line-length=100 --max-file-length=500 --max-name-length=40 --max-name-length=40 --min-name-length=4
sonar.swift.tailor.report=sonar-reports/*tailor.txt
# login info
sonar.host.url=http://localhost:9000
sonar.login=admin
sonar.password=xxx
# database info
sonar.jdbc.username=root
sonar.jdbc.password=xxx
sonar.jdbc.url=jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance&useSSL=false
???
-
run-sonar-swift.sh
和sonar-project.properties
如果不想以手動的形式放入工程目錄下浑玛,可將run-sonar-swift.sh
放在服務器某個目錄下绍申,將sonar-project.properties
配置寫入Jenkins構(gòu)建腳本中噩咪,在執(zhí)行Jenkins構(gòu)建時通過腳本將run-sonar-swift.sh
copy到工程根目錄下,將sonar-project.properties
寫入工程根目錄下极阅。這樣即使多個項目胃碾,也不用單獨修改項目工程,僅修改腳本即可筋搏。掃描OC項目的Jenkins腳本如下:
source /Users/melo/.bash_profile
export LANG="en_US.UTF-8"
export LANGUAGE="en_US.UTF-8"
export LC_ALL="en_US.UTF-8"
cd $WORKSPACE
pod install --verbose --no-repo-update
rm -f run-sonar.sh
rm -f sonar-project.properties
cat>sonar-project.properties<<EOF
##########################
# Required configuration #
##########################
# sonar平臺中相對應項目的key
sonar.projectKey=XXX
# sonar平臺中相對應項目的名字
sonar.projectName=XXX
# 版本號
sonar.projectVersion=6.0.0
# Encoding of the source code
sonar.sourceEncoding=UTF-8
# 語言 ObjC / Swift
sonar.language=ObjC
# sonar檢測的源文件目錄仆百,‘.’表示當前根目錄下的所有文件目錄;包含主要源文件的目錄的逗號分隔路徑
sonar.sources=.
# 檢測中的測試源文件(指定單元測試文件)
# sonar.test.inclusions=**/*Test*/**
# sonar.test.inclusions=*.swift,*.m
# 檢測中排除的源文件(排除的源文件不參與檢測,一般排除單元測試文件奔脐、配置文件等)
# sonar.exclusions=XXX1,XXX2,XXX3……
# 項目描述
sonar.projectDescription=XXX iOS with the SonarScanner
# sonar檢測的測試文件目錄俄周,‘.’表示當前根目錄下的所有文件目錄;包含測試源文件的目錄的逗號分隔路徑吁讨。從構(gòu)建系統(tǒng)中讀取Maven,Gradle峦朗,MSBuild項目建丧。否則默認為空。
# sonar.tests=XXXTests,XXXUITests
# 檢測中排除的測試源文件(排除的源文件不參與檢測)
# sonar.test.exclusions=
# 模擬器配置
sonar.objectivec.simulator=platform=iOS Simulator,name=iPhone 11 Pro Max,OS=latest
# scheme Configuration Debug/Release
sonar.objectivec.appConfiguration=Debug
# Xcode 工程文件配置 xcodeproj波势、xcworkspace
sonar.objectivec.project=XXX.xcodeproj
sonar.objectivec.workspace=XXX.xcworkspace
# Xcode Scheme
sonar.objectivec.appScheme=TuyaSmartPublic
# App 名稱
sonar.objectivec.appName=XXX
##########################
# Optional configuration #
##########################
# SCM
sonar.scm.enabled=true
# sonar.scm.url=scm:git:http://xxx
# JUnit report generated
sonar.junit.reportsPath=sonar-reports/
# Lizard report generated
sonar.objectivec.lizard.report=sonar-reports/lizard-report.xml
# Coverage report generated
sonar.objectivec.coverage.reportPattern=sonar-reports/coverage-objectivec*.xml
# 報告的名字
sonar.objectivec.oclint.report=oclint.xml
# 報告的路徑
sonar.objectivec.oclint.reportPath=sonar-reports/oclint.xml
# Swiftlint report generated
#sonar.swift.swiftlint.report=sonar-reports/*swiftlint.txt
# Paths to exclude from coverage report (surefire, 3rd party libraries etc.)
# sonar.swift.excludedPathsFromCoverage=pattern1,pattern2
sonar.objectivec.excludedPathsFromCoverage=.*Tests.*
# Tailor report generated
sonar.objectivec.tailor.config=--no-color --max-line-length=100 --max-file-length=500 --max-name-length=40 --max-name-length=40 --min-name-length=4
sonar.objectivec.tailor.report=sonar-reports/*tailor.txt
sonar.objectivec.coverageType=legacy
# login info
sonar.host.url=http://localhost:9000
sonar.login=XXX
sonar.password=XXX
# database info
sonar.jdbc.username=root
sonar.jdbc.password=12345678
sonar.jdbc.url=jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance&useSSL=false
sonar.verbose=true
EOF
cp /Users/XXX/Documents/Sonar/run-sonar.sh $WORKSPACE
./run-sonar.sh -v
- 配置插件規(guī)則翎朱,在掃描文件下添加.swiftlint.yml文件,根據(jù)自身代碼的情況尺铣,選擇性的忽略部分規(guī)則拴曲,或者修改swift插件規(guī)則。
use_nested_configs: true
# 執(zhí)行 linting 時忽略的路徑凛忿。 優(yōu)先級比 `included` 更高澈灼。
excluded:
- Pods
# 執(zhí)行時排除掉的規(guī)則
disabled_rules:
- force_cast # 強制轉(zhuǎn)換
- force_try # try語句判斷
- line_length # 單行代碼長度
#代碼復雜度
cyclomatic_complexity:
warning: 19
error: 20
# 文件長度
file_length:
warning: 999
error: 1000
# 實體類長度
type_body_length:
warning: 999
error: 1000
# 函數(shù)體長度
function_body_length:
warning: 99
error: 100
# 命名規(guī)則可以設置最小長度和最大程度的警告/錯誤。此外它們也可以設置排除在外的名字
type_name:
min_length: 4 # 只是警告
max_length: # 警告和錯誤
warning: 40
error: 50
# excluded: iPhone # 排除某個名字
#variable_name:
# min_length: # 只有最小長度
# error: 4 # 只有錯誤
# excluded: # 排除某些名字
# - id
# - URL
# - GlobalAPIKey
reporter: "xcode" # 報告類型 (xcode, json, csv, checkstyle)
???
更多規(guī)則店溢,可參考
- SonarQube規(guī)則整理
- Realm官方公布的 SwiftLint
- Github 公布的 Swift 代碼規(guī)范--原文
- Github 公布的 Swift 代碼規(guī)范--中文
注:.swiftlint.yml 文件必須放在掃描的具體路徑下蕉汪,放在工程根目錄下無效。
6. 配置變量配置
open ~/.bash_profile
# jdk
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/ Contents/Home
export CLASSPATH=.:${JAVA_HOME}/lib/dt.jar:${JAVA_HOME}/lib/ tools.jar
#export PATH=${JAVA_HOME}/bin:$PATH
# maven
export M3_HOME=/usr/local/Cellar/maven/3.6.3_1/libexec
export PATH=${JAVA_HOME}/bin:$M3_HOME/bin:$PATH
# gradle
export GRADLE_HOME=/usr/local/Cellar/gradle/4.1
export PATH=$PATH:$GRADLE_HOME/bin
# mysql
export PATH=${PATH}:/usr/local/mysql/bin
export PATH=$PATH:/usr/local/mysql/support-files
# postgresql
export PATH=${PATH}:/usr/local/Cellar/postgresql/13.1/bin
# sonarqube
export SONARQUBE=/usr/local/sonarqube-7.8
export PATH=$SONARQUBE/bin:$PATH
# sonar-scanner
export SONAR_SCANNER=/usr/local/sonar-scanner-4.5.0.2216-macosx
export PATH=$SONAR_SCANNER/bin:$PATH
# python
PYTHON=/Library/Frameworks/Python.framework/Versions/3.8
export PATH=$PYTHON/bin:$PATH
alias python="/Library/Frameworks/Python.framework/Versions/3.8/bin/ python3.8"
#PYTHON=/System/Library/Frameworks/Python.framework/Versions/ 2.7
#export PATH=$PYTHON/bin:$PATH
#alias python="/System/Library/Frameworks/Python.framework/Versions/ 2.7/bin/python2.7"
# oclint
export OCLINT=/usr/local/Cellar/oclint/0.13.1
export PATH=$OCLINT_HOME/bin:$PATH
# swiftlint
export SWIFTLINT=/usr/local/Cellar/swiftlint/0.41.0
export PATH=$SWIFTLINT/bin:$PATH
# xcpretty
XCPRETTY=/usr/local/bin/xcpretty
export PATH=$XCPRETTY:$PATH
# tailor
export TAILOR=/usr/local/Cellar/tailor/0.12.0_1
export PATH=$TAILOR/bin:$PATH
# lizard
LIZARD=/usr/local/bin/lizard
export PATH=$LIZARD:$PATH
# slather
SLATHER=/usr/local/bin/slather
export PATH=$SLATHER:$PATH
# xctool
export XCTOOL=/usr/local/Cellar/xctool/0.3.7
export PATH=$XCTOOL/bin:$PATH
# gcovr
export GCOVR=/usr/local/Cellar/gcovr/4.2_1
export PATH=$GCOVR/bin:$PATH
# xcodebuild
XCODEBUILD=/usr/bin/xcodebuild
export PATH=$XCODEBUILD:$PATH
# local
export USRLOCAL=/usr/local
export PATH=$USRLOCAL/bin:$PATH
保存環(huán)境配置
source ~/.bashrc
7. 源碼工程配置
Tagrget配置:Tests逞怨、UITests必不可少
8. Jenkins構(gòu)建掃描
創(chuàng)建Jenkins掃描任務
配置源碼
??? 關(guān)于SonrQube 分析iOS項目的詳細配置者疤,可參考SonarQube Integration with iOS
配置Jenkins掃描腳本
source /Users/xxxx/.bash_profile
export LC_ALL="en_US.UTF-8"
cd $WORKSPACE
./run-sonar-swift.sh -v
掃描成功結(jié)果圖
掃描日志
SonarQube分析結(jié)果
導出報告
社區(qū)版導出報告需要安裝開源插件sonar-cnes-report.
這里因為部署的是sonar7.8的版本,與sonar-cnes-report
最新的4.0.0版本不兼容叠赦,新增插件后導致sonar重啟失敗驹马。選擇一個較低的版本即可,這里選擇3.3.1版本即可除秀,其他版本也可以嘗試一下糯累。
將下載的 jar包復制到中sonarqube的plugins目錄下,重新啟動 sonarqube册踩,然后單擊“更多”>“CNES 報告”泳姐。
注意:由于個人安裝sonarqube的目錄各不相同,這里plugins目錄為:/usr/local/sonarqube-7.8/extensions/plugins暂吉,選擇自己的目錄即可胖秒。
常見錯誤:
1、Server接受的數(shù)據(jù)包大小慕的,會受 max_allowed_packet 參數(shù)限制阎肝,導致寫入或者更新失敗。
11:44:53.491 ERROR: Error during SonarScanner execution
Failed to upload report - An error has occurred. Please contact your administrator
+ returnValue=2
+ set +x
ERROR - Command 'sonar-scanner -X -Dsonar.verbose=true' failed with error code: 2
解決方案:修改MySQL的max_allowed_packet的大小肮街。
關(guān)于max_allowed_packet的修改风题,請參考:MYSQL 5.7 無法修改max_allowed_packet參數(shù)?
??? 到這里,SonarQube代碼掃描結(jié)束沛硅,接下來就是根據(jù)分析結(jié)果眼刃,做針對性的整改代碼。我們目前的策略把定義為阻斷摇肌、嚴重鸟整、主要問題清零,次要朦蕴、提示這兩類問題先不處理篮条,后期有時間再處理。中間針對一些不合理的維度吩抓,比如:循環(huán)復雜度到達10定義為嚴重問題涉茧,文件、類代碼行數(shù)限制200行疹娶,超過定義主要問題伴栓,單行代碼長度限制120字符等等骑脱,做了插件規(guī)則的調(diào)整:忽略/修改閾值磕仅。總之一句話:具體問題具體分析炬藤,根據(jù)自家代碼實際情況做評估额港,如果代碼整改需要大刀闊斧饺窿,那跟重構(gòu)無異了,厲害關(guān)系需要權(quán)衡清楚移斩。另外SonarQube問題的整改肚医,盡量放在項目中去做,迭代周期太短向瓷,開發(fā)肠套、測試很難把控到位。如果一定要放在迭代中來做猖任,那一定要排好整改計劃你稚,按節(jié)奏推進。