Kotlin Android項目可用的靜態檢查工具: Android官方的Lint, 第三方的ktlint和detekt. ...
Kotlin Android項目靜態檢查工具的使用
Kotlin Android項目可用的靜態檢查工具: Android官方的Lint, 第三方的ktlint和detekt.
靜態檢查工具
靜態檢查工具, 指不需要運行代碼, 對代碼進行檢查的工具.
不止代碼風格, 還可以檢查代碼的正確性, 是否有安全問題, 是否有性能問題等.
靜態檢查工具一般都具備可擴展性, 方便使用者制定和添加自己的規則.
比較流行的Java靜態檢查工具有CheckStyle, FindBugs, PMD等.
Android項目, 用Kotlin語言, 可用的靜態檢查工具: 官方的Android Lint, ktlint和detekt.
Android Lint
Android官方提供了代碼掃描工具, 叫lint.
在Android Studio運行lint, 在菜單中選:
Analyze -> Inspect Code...
, 選擇範圍, 點OK
即可運行.
也可以在命令行:
./gradlew lint
會分類報告出各種各樣的錯誤.
查看所有的Lint規則
點擊菜單中的Analyze -> Inspect Code...
, 在彈框的Inspection profile
部分點擊表示更多的...
按鈕, 會彈出框顯示當前的所有lint規則.
可以選擇是否包括規則, 編輯它們的優先順序和應用範圍等.
Android Lint配置
全局的配置除了用IDE, 還可以通過lint.xml
文件.
還可以在gradle中配置, 比如:
android {
...
lintOptions {
// Turns off checks for the issue IDs you specify.
disable 'TypographyFractions','TypographyQuotes'
// Turns on checks for the issue IDs you specify. These checks are in
// addition to the default lint checks.
enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
// To enable checks for only a subset of issue IDs and ignore all others,
// list the issue IDs with the 'check' property instead. This property overrides
// any issue IDs you enable or disable using the properties above.
check 'NewApi', 'InlinedApi'
// If set to true, turns off analysis progress reporting by lint.
quiet true
// if set to true (default), stops the build if errors are found.
abortOnError false
// if true, only report errors.
ignoreWarnings true
}
}
特定代碼可以利用@SuprressLint
註解.
比如: @SuppressLint("NewApi")
, @SuppressLint("all")
.
特定xml文件中可以用tools:ignore
, 比如tools:ignore="UnusedResources"
, tools:ignore="NewApi,StringFormatInvalid"
, tools:ignore="all"
等.
Android Lint現狀基準快照
可以在gradle中進行設置一個baseline:
android {
lintOptions {
baseline file("lint-baseline.xml")
}
}
第一次添加這個, 運行lint之後, 會自動創建一個lint-baseline.xml
文件, 所有當前的問題都會被寫入這個文件.
當再次運行lint, 就只會讀這個文件, 作為基準, 只報告新的錯誤或警告. 並且提示其他原有問題在baseline文件中, 被排除.
如果想重新生成baseline可以手動刪除這個文件, 重新進行.
ktlint
代碼風格檢查, 遵循的是: Kotlin style guide
所有的規則代碼:
github: ktlint rule set
ktlint安裝
在app/build.gradle
中添加:
configurations {
ktlint
}
dependencies {
ktlint "com.pinterest:ktlint:0.36.0"
...
}
task ktlint(type: JavaExec, group: "verification") {
description = "Check Kotlin code style."
classpath = configurations.ktlint
main = "com.pinterest.ktlint.Main"
args "src/**/*.kt"
// to generate report in checkstyle format prepend following args:
// "--reporter=plain", "--reporter=checkstyle,output=${buildDir}/ktlint.xml"
// see https://github.com/pinterest/ktlint#usage for more
}
check.dependsOn ktlint
task ktlintFormat(type: JavaExec, group: "formatting") {
description = "Fix Kotlin code style deviations."
classpath = configurations.ktlint
main = "com.pinterest.ktlint.Main"
args "-F", "src/**/*.kt"
}
ktlint使用
檢查:
./gradlew ktlint
自動修改:
./gradlew ktlintFormat
並不是所有問題都可以自動修改, 有的問題需要手動改.
配置中的這句:
check.dependsOn ktlint
把運行檢查加到了check task中. 通過:
./gradlew check --dry-run
可以查看運行check
task都會做哪些事情(dry run表示不用實際執行這些任務).
ktlint源碼
看ktlint的源碼, 程式的入口是Main.kt
文件中的main
.
使用的規則集是:
class StandardRuleSetProvider : RuleSetProvider {
// Note: some of these rules may be disabled by default. See the default .editorconfig.
override fun get(): RuleSet = RuleSet(
"standard",
ChainWrappingRule(),
CommentSpacingRule(),
FilenameRule(),
FinalNewlineRule(),
ImportOrderingRule(),
IndentationRule(),
MaxLineLengthRule(),
ModifierOrderRule(),
NoBlankLineBeforeRbraceRule(),
NoConsecutiveBlankLinesRule(),
NoEmptyClassBodyRule(),
NoLineBreakAfterElseRule(),
NoLineBreakBeforeAssignmentRule(),
NoMultipleSpacesRule(),
NoSemicolonsRule(),
NoTrailingSpacesRule(),
NoUnitReturnRule(),
NoUnusedImportsRule(),
NoWildcardImportsRule(),
ParameterListWrappingRule(),
SpacingAroundColonRule(),
SpacingAroundCommaRule(),
SpacingAroundCurlyRule(),
SpacingAroundDotRule(),
SpacingAroundKeywordRule(),
SpacingAroundOperatorsRule(),
SpacingAroundParensRule(),
SpacingAroundRangeOperatorRule(),
StringTemplateRule()
)
}
可以看到都是代碼格式相關的規則.
線上查看: ktlint ruleset standard
detekt
Github: detekt也是一個Kotlin的靜態分析工具.
官方網站: detekt.
detekt安裝
在項目根目錄的build.gradle
文件中添加:
plugins {
id("io.gitlab.arturbosch.detekt").version("1.5.0")
}
allprojects {
repositories {
google()
jcenter()
}
apply plugin: 'io.gitlab.arturbosch.detekt'
}
detekt {
failFast = true // fail build on any finding
buildUponDefaultConfig = true // preconfigure defaults
config = files("$projectDir/config/detekt.yml")
// point to your custom config defining rules to run, overwriting default behavior
baseline = file("$projectDir/config/baseline.xml")
// a way of suppressing issues before introducing detekt
reports {
html.enabled = true // observe findings in your browser with structure and code snippets
xml.enabled = true // checkstyle like format mainly for integrations like Jenkins
txt.enabled = true
// similar to the console output, contains issue signature to manually edit baseline files
}
}
運行./gradlew tasks
可以看到相關任務被加進去了:
detekt
detektBaseline - Creates a detekt baseline on the given --baseline path.
detektGenerateConfig - Generate a detekt configuration file inside your project.
detektIdeaFormat - Uses an external idea installation to format your code.
detektIdeaInspect - Uses an external idea installation to inspect your code.
detekt使用
運行:
./gradlew detekt
進行檢測.
detekt {
}
塊是用來進行自定義的屬性設置的.
如果開啟了文件輸出, 結果在:
app/build/reports/detekt
目錄下可以查看.
並且運行./gradlew check --dry-run
可以發現, 運行check
的時候也會執行detekt
.
可以利用:
./gradlew detektGenerateConfig
生成配置文件: config/detekt/detekt.yml
.
在配置文件中可以看到對各種規則的開關狀態, 編輯它可以進行定製.
detekt源碼
程式的入口在detekt-cli
module的Main.kt
.
根據參數的不同返回不同的Runner
.
主要用於檢測的是這個Runner, 它的執行方法:
override fun execute() {
createSettings().use { settings ->
val (checkConfigTime) = measure { checkConfiguration(settings) }
settings.debug { "Checking config took $checkConfigTime ms" }
val (serviceLoadingTime, facade) = measure { DetektFacade.create(settings) }
settings.debug { "Loading services took $serviceLoadingTime ms" }
var (engineRunTime, result) = measure { facade.run() }
settings.debug { "Running core engine took $engineRunTime ms" }
checkBaselineCreation(result)
result = transformResult(result)
val (outputResultsTime) = measure { OutputFacade(arguments, result, settings).run() }
settings.debug { "Writing results took $outputResultsTime ms" }
if (!arguments.createBaseline) {
checkBuildFailureThreshold(result, settings)
}
}
}
讀取配置, 創建服務, 執行檢測, 轉化輸出結果.
當沒有用戶自己聲明的配置文件時, 使用的預設配置文件是default-detekt-config.yml.
在這裡可以看到對所有規則的預設開關狀態.
detekt的規則代碼放在detekt-rules
這個module下:
detekt rules.
官方文檔上也有說明, 比如這是performance分類下的rules: detekt performance.
包含ktlint的規則
detekt的規則種類更多, 並且它包含了ktlint的代碼檢查規則.
所有的ktlint
規則被包裝在detekt-formatting
這個module下, 見: detekt formatting rules.
但是並不是所有規則都預設開啟.
在配置文件的formatting
塊可以查看具體的規則開關狀態.
如果需要修改定製, 需要生成自己的配置文件.
和Git hook結合
Lint工具的運行可以放在CI上, 也可以每次本地手動跑, 這樣在push之前就能發現錯誤並改正.
一種避免忘記的方法就是使用Git hook.
項目的.git/hooks
文件夾下自帶一些有意思的sample.
如何使用git hook
只要在.git/hooks
路徑下添加對應名字的文件, 如pre-push
, pre-commit
, 寫入想要的代碼即可.
hook文件返回0表示通過, 會進行下一步操作.
否則後面的操作會被放棄.
註意文件要有執行許可權:
chmod +x 文件名
如果不想跑hook可以加上 --no-verify
比如
git commit --no-verify
在提交之前運行靜態檢查
以ktlint為例, 我們希望每次commit的時候先運行./gradlew ktlint
, 如果有issue則放棄提交.
在.git/hooks
中添加pre-commit
文件, 其中:
#!/bin/bash
echo "Running ktlint"
./gradlew ktlint
result=$?
if [ "$result" = 0 ] ; then
echo "ktlint found no problems"
exit 0
else
echo "Problems found, files will not be committed."
exit 1
fi
detekt的添加也是類似, 官網上給了一個例子:
detekt/git-pre-commit-hook.
如何track, 共用Git hooks
因為hooks文件在.git
目錄里, 只是在本地, 那麼在團隊成員間如何共用呢?
根據這個問題:
https://stackoverflow.com/questions/427207/can-git-hook-scripts-be-managed-along-with-the-repository
從git 2.9開始, 可以設置:core.hooksPath
了.
可以在repo裡面添加一個目錄hooks, 然後把git hooks文件放進去track.
在命令行跑:
git config core.hooksPath hooks
把找hook文件的目錄設置成指定目錄就好了.
總結
本文介紹了三種靜態檢查的工具:
- Android Lint: Android自帶的Lint檢查. 檢查的內容比較豐富.
- ktlint: Kotlin代碼樣式規範檢查. 特色: 有自動修正命令.
- detekt: 除了code style還有performance, exceptions, bugs等方面的檢查. formatting模塊包含了ktlint的所有style檢查(雖然有的具體的規則預設不開啟, 但是可以配置), 是前者的超集.
這些工具都可以擴展, 添加自定義的檢查規則.
結合Git hook把靜態檢查在本地進行, 可以實現問題的儘早暴露和修改.
示例代碼
- ktlint集成: ktlint分支.
- detekt集成: detekt分支.