Kotlin Android项目可用的静态检查工具: Android官方的Lint, 第三方的ktlint和detekt.
静态检查工具, 指不需要运行代码, 对代码进行检查的工具.
不止代码风格, 还可以检查代码的正确性, 是否有安全问题, 是否有性能问题等.
静态检查工具一般都具备可扩展性, 方便使用者制定和添加自己的规则.
比较流行的Java静态检查工具有CheckStyle, FindBugs, PMD等.
Android项目, 用Kotlin语言, 可用的静态检查工具: 官方的Android Lint, ktlint和detekt.
Android官方提供了代码扫描工具, 叫lint.
在Android Studio运行lint, 在菜单中选:
Analyze -> Inspect Code...
, 选择范围, 点OK
即可运行.
也可以在命令行:
./gradlew lint 复制代码
会分类报告出各种各样的错误.
点击菜单中的Analyze -> Inspect Code...
, 在弹框的Inspection profile
部分点击表示更多的...
按钮, 会弹出框显示当前的所有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"
等.
可以在gradle中进行设置一个baseline:
android { lintOptions { baseline file("lint-baseline.xml") } } 复制代码
第一次添加这个, 运行lint之后, 会自动创建一个lint-baseline.xml
文件, 所有当前的问题都会被写入这个文件.
当再次运行lint, 就只会读这个文件, 作为基准, 只报告新的错误或警告. 并且提示其他原有问题在baseline文件中, 被排除.
如果想重新生成baseline可以手动删除这个文件, 重新进行.
代码风格检查, 遵循的是: Kotlin style guide
所有的规则代码: github: ktlint rule set
在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" } 复制代码
检查:
./gradlew ktlint 复制代码
自动修改:
./gradlew ktlintFormat 复制代码
并不是所有问题都可以自动修改, 有的问题需要手动改.
配置中的这句:
check.dependsOn ktlint 复制代码
把运行检查加到了check task中. 通过:
./gradlew check --dry-run 复制代码
可以查看运行check
task都会做哪些事情(dry run表示不用实际执行这些任务).
看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
Github: detekt也是一个Kotlin的静态分析工具.
官方网站: 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. 复制代码
运行:
./gradlew detekt 复制代码
进行检测.
detekt { } 复制代码
块是用来进行自定义的属性设置的.
如果开启了文件输出, 结果在:
app/build/reports/detekt
目录下可以查看.
并且运行./gradlew check --dry-run
可以发现, 运行check
的时候也会执行detekt
.
可以利用:
./gradlew detektGenerateConfig 复制代码
生成配置文件: config/detekt/detekt.yml
.
在配置文件中可以看到对各种规则的开关状态, 编辑它可以进行定制.
程序的入口在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.
detekt的规则种类更多, 并且它包含了ktlint的代码检查规则.
所有的ktlint
规则被包装在detekt-formatting
这个module下, 见: detekt formatting rules.
但是并不是所有规则都默认开启.
在配置文件的formatting
块可以查看具体的规则开关状态.
如果需要修改定制, 需要生成自己的配置文件.
Lint工具的运行可以放在CI上, 也可以每次本地手动跑, 这样在push之前就能发现错误并改正.
一种避免忘记的方法就是使用Git hook.
项目的.git/hooks
文件夹下自带一些有意思的sample.
只要在.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.
因为hooks文件在.git
目录里, 只是在本地, 那么在团队成员间如何共享呢?
根据这个问题: stackoverflow.com/questions/4…
从git 2.9开始, 可以设置:core.hooksPath
了.
可以在repo里面添加一个目录hooks, 然后把git hooks文件放进去track.
在命令行跑:
git config core.hooksPath hooks 复制代码
把找hook文件的目录设置成指定目录就好了.
本文介绍了三种静态检查的工具:
这些工具都可以扩展, 添加自定义的检查规则.
结合Git hook把静态检查在本地进行, 可以实现问题的尽早暴露和修改.