⚠️本文为稀土技术社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!
前语
各个团队多少都有一些自己的代码标准,但拟定代码标准简单,困难的是如何落地。假如彻底依靠人力Code Review
难免有所遗漏。
这个时分就需求经过静态代码查看东西在每次提交代码时主动查看,本文首要介绍如何运用DeteKt
落地Kotlin
代码标准,首要包含以下内容
- 为什么运用
DeteKt
? -
IDE
接入DeteKt
插件 -
CLI
命令行办法接入DeteKt
-
Gradle
办法接入DeteKt
- 自界说
Detekt
检测规矩 -
Github Action
集成Detekt
检测
为什么运用DeteKt
?
说起静态代码查看,咱们首先想起来的或许是lint
,比较DeteKt
只支撑Kotlin
代码,lint
不只支撑Kotlin
,Java
代码,也支撑资源文件标准查看,那么咱们为什么不运用Lint
呢?
在我看来,Lint
在运用上首要有两个问题:
- 与
IDE
集成不够好,自界说lint
规矩的正告只要在运行./gradlew lint
后才会在IDE
上展现出来,在clean
之后又会消失 -
lint
查看速度较慢,尤其是大型项目,只对增量代码进行查看的逻辑需求自界说
而DeteKt
供给了IDE
插件,敞开后可直接在IDE
中查看正告,这样能够在第一时间发现问题,防止后续查看发现问题后再修正流程过长的问题
一起Detekt
支撑CLI
命令行办法接入与Gradle
办法接入,支撑只查看新增代码,在查看速度上比起lint
也有一定的优势
IDE
接入DeteKt
插件
假如能在IDE
中提示代码中存在的问题,应该是最快发现问题的办法,DeteKt
也交心的为咱们预备了插件,如下所示:
首要能够装备以下内容:
-
DeteKt
开关 - 格式化开关,
DeteKt
直接运用了ktlint
的规矩 -
Configuration file
:规矩装备文件,能够在其间装备各种规矩的开关与参数,默许装备可见:default-detekt-config.yml -
Baseline file
:基线文件,越过旧代码问题,有了这个基线文件,下次扫描时,就会绕过文件中列出的基线问题,而只提示新增问题。 -
Plugin jar
: 自界说规矩jar
包,在自界说规矩后打出jar
包,在扫描时就能够运用自界说规矩了
DeteKt IDE
插件能够实时提示问题(包含自界说规矩),如下图所示,咱们增加了自界说制止运用kae
的规矩:
对于一些支撑主动修复的格式问题,DeteKt
插件支撑主动格式化,一起也能够装备快捷键,一键主动格式化,如下所示:
CLI
命令行办法接入DeteKt
DeteKt
支撑经过CLI
命令行办法接入,支撑只检测几个文件,比方本次commit
提交的文件
咱们能够经过如下办法,下载DeteKt
的jar
然后运用
curl -sSLO https://github.com/detekt/detekt/releases/download/v1.22.0-RC1/detekt-cli-1.22.0-RC1.zip
unzip detekt-cli-1.22.0-RC1.zip
./detekt-cli-1.22.0-RC1/bin/detekt-cli --help
DeteKt CLI
支撑许多参数,下面列出一些常用的,其他能够拜见:Run detekt using Command Line Interface
Usage: detekt [options]
Options:
--auto-correct, -ac
支撑主动格式化的规矩主动格式化,默许为false
Default: false
--baseline, -b
假如传入了baseline文件,只要不在baseline文件中的问题才会掘出来
--classpath, -cp
实验特性:传入依靠的class途径和jar的途径,用于类型解析
--config, -c
规矩装备文件,能够装备规矩开关及参数
--create-baseline, -cb
创立baseline,默许false,假如敞开会创立出一个baseline文件,供后续运用
--input, -i
输入文件途径,多个途径之间用逗号衔接
--jvm-target
EXPERIMENTAL: Target version of the generated JVM bytecode that was
generated during compilation and is now being used for type resolution
(1.6, 1.8, 9, 10, 11, 12, 13, 14, 15, 16 or 17)
Default: 1.8
--language-version
为支撑类型解析,需求传入java版别
--plugins, -p
自界说规矩jar途径,多个途径之间用,或许;衔接
在命令行能够直接经过如下办法查看
java -jar /path/to/detekt-cli-1.21.0-all.jar # detekt-cli-1.21.0-all.jar地点途径
-c /path/to/detekt_1.21.0_format.yml # 规矩装备文件地点途径
--plugins /path/to/detekt-formatting-1.21.0.jar # 格式化规矩jar,首要基于ktlint封装
-ac # 敞开主动格式化
-i $FilePath$ # 需求扫描的源文件,多个途径之间用,或许;衔接
经过如上办法进行代码查看速度是非常快的,依据经验来说一般便是几秒之内能够完结,因此咱们完结能够将DeteKt
与git hook
结合起来,在每次提交commit
的时分进行检测,而假如是一些比较耗时的东西比方lint
,应该是做不到这一点的
类型解析
上面咱们提到了,DeteKt
的--classpth
参数与--language-version
参数,这些是用于类型解析的。
类型解析是DeteKt
的一项功用,它允许 Detekt
对您的 Kotlin
源代码执行更高档的静态剖析。
一般,Detekt
在编译期间无法拜访编译器语义剖析的成果,咱们只能获取Kotlin
源代码的笼统语法树,却无法知道语法树上符号的语义,这约束了咱们的查看能力,比方咱们无法判别符号的类型,两个符号终究是不是同一个对象等
经过启用类型解析,Detekt
能够获取Kotlin
编译器语义剖析的成果,这让咱们能够自界说一些更高档的查看。
而要获取类型与语义,当然要传入依靠的class
,也便是classpath
,比方android
项目中常常需求传入android.jar
与kotlin-stdlib.jar
Gradle
办法接入DeteKt
CLI
办法检测尽管快,但是需求手动传入classpath
,比较麻烦,尤其是有时分自界说规矩需求解析咱们自己的类而不是kotlin-stdlib.jar
中的类时,那么就需求将项目中的代码的编译成果传入作为classpath
了,这样就更麻烦了
DeteKt
同样支撑Gradle
插件办法接入,这种办法不需求咱们别的再装备classpath
,咱们能够将CLI
命令行办法与Gradle
办法结合起来,在本地经过CLI
办法快速检测,在CI
上经过Gradle
插件进行完整的检测
接入步骤
// 1. 引进插件
plugins {
id("io.gitlab.arturbosch.detekt").version("[version]")
}
repositories {
mavenCentral()
}
// 2. 装备插件
detekt {
config = files("$projectDir/config/detekt.yml") // 规矩装备
baseline = file("$projectDir/config/baseline.xml") // baseline装备
parallel = true
}
// 3. 自界说规矩
dependencies {
detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:1.21.0"
detektPlugins project(":customRules")
}
// 4. 装备 jvmTarget
tasks.withType(Detekt).configureEach {
jvmTarget = "1.8"
}
// DeteKt Task用于检测,DetektCreateBaselineTask用于创立Baseline
tasks.withType(DetektCreateBaselineTask).configureEach {
jvmTarget = "1.8"
}
// 5. 只剖析指定文件
tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
// include("**/special/package/**") // 只剖析 src/main/kotlin 下面的指定目录文件
exclude("**/special/package/internal/**") // 过滤指定目录
}
如上所示,接入首要需求做这么几件事:
- 引进插件
- 装备插件,首要是装备
config
与baseline
,即规矩开关与老代码过滤 - 引进
detekt-formatting
与自界说规矩的依靠 - 装备
JvmTarget
,用于类型解析,但不必再装备classpath
了。 - 除了
baseline
之外,也能够经过include
与exclude
的办法指定只扫描指定文件的办法来完结增量检测
经过以上办法就接入成功了,运行./gradlew detektDebug
就能够开始检测了,扫描成果可在终端直接查看,并能够直接定位到问题代码处,也能够在build/reprots/
途径下查看输出的陈述文件:
自界说Detekt
检测规矩
要落地自己拟定的代码标准,不可防止的需求自界说规矩,当然咱们首先要看下DeteKt
自带的规矩,是否已经有咱们需求的,只需把开关翻开即可.
DeteKt
自带规矩
DeteKt
自带的规矩都能够经过开关装备,假如没有在 Detekt
闭包中指定 config
属性,detekt
会运用默许的规矩。这些规矩选用 yaml
文件描述,运行 ./gradlew detektGenerateConfig
会生成 config/detekt/detekt.yml
文件,咱们能够在这个文件的基础上拟定代码标准原则。
detekt.yml
中的每条规矩形如:
complexity: # 大类
active: true
ComplexCondition: # 规矩名
active: true # 是否启用
threshold: 4 # 有些规矩,能够设定一个阈值
# ...
更多关于装备文件的修正办法,请参看官方文档-装备文件
Detekt
的规矩集划分为 9 个大类,每个大类下有详细的规矩:
规矩大类 | 阐明 |
---|---|
comments | 与注释、文档有关的标准查看 |
complexity | 查看代码复杂度,复杂度过高的代码不利于保护 |
coroutines | 与协程有关的标准查看 |
empty-blocks | 空代码块查看,空代码应该尽量防止 |
exceptions | 与反常抛出和捕获有关的标准查看 |
formatting | 格式化问题,detekt直接引证的 ktlint 的格式化规矩集 |
naming | 类名、变量命名相关的标准查看 |
performance | 查看潜在的性能问题 |
potentail-bugs | 查看潜在的BUG |
style | 一致团队的代码风格,也包含一些由 Detekt 界说的格式化问题 |
表格引证自:cloud.tencent.com/developer/a…
更细节的规矩阐明,请参看:官方文档-规矩集阐明
自界说规矩
接下来咱们自界说一个检测KAE
运用的规矩,如下所示:
// 进口
class CustomRuleSetProvider : RuleSetProvider {
override val ruleSetId: String = "detekt-custom-rules"
override fun instance(config: Config): RuleSet = RuleSet(
ruleSetId,
listOf(
NoSyntheticImportRule(),
)
)
}
// 自界说规矩
class NoSyntheticImportRule : Rule() {
override val issue = Issue(
"NoSyntheticImport",
Severity.Maintainability,
"Don’t import Kotlin Synthetics as it is already deprecated.",
Debt.TWENTY_MINS
)
override fun visitImportDirective(importDirective: KtImportDirective) {
val import = importDirective.importPath?.pathStr
if (import?.contains("kotlinx.android.synthetic") == true) {
report(
CodeSmell(
issue,
Entity.from(importDirective),
"'$import' 不要运用kae,推荐运用viewbinding"
)
)
}
}
}
代码其实并不复杂,首要做了这么几件事:
- 增加
CustomRuleSetProvider
作为自界说规矩的进口,并将NoSyntheticImportRule
增加进去 - 完结
NoSyntheticImportRule
类,首要包含issue
与各种visitXXX
办法 -
issue
属性用于界说在控制台或任何其他输出格式上打印的ID
、严重性和提示信息 -
visitImportDirective
即经过拜访者模式拜访语法树的回调,当拜访到import
时会回调,咱们在这儿检测有没有增加kotlinx.android.synthetic
,发现存在则陈述反常
支撑类型解析的自界说规矩
上面的规矩没有用到类型解析,也便是说不传入classpath
也能运用,咱们现在来看一个需求运用类型解析的自界说规矩
比方咱们需求在项目中制止直接运用android.widget.Toast.show
,而是运用咱们一致封装的东西类,那么咱们能够自界说如下规矩:
class AvoidToUseToastRule : Rule() {
override val issue = Issue(
"AvoidUseToastRule",
Severity.Maintainability,
"Don’t use android.widget.Toast.show",
Debt.TWENTY_MINS
)
override fun visitReferenceExpression(expression: KtReferenceExpression) {
super.visitReferenceExpression(expression)
if (expression.text == "makeText") {
// 经过bindingContext获取语义
val referenceDescriptor = bindingContext.get(BindingContext.REFERENCE_TARGET, expression)
val packageName = referenceDescriptor?.containingPackage()?.asString()
val className = referenceDescriptor?.containingDeclaration?.name?.asString()
if (packageName == "android.widget" && className == "Toast") {
report(
CodeSmell(
issue, Entity.from(expression), "制止直接运用Toast,主张运用xxxUtils"
)
)
}
}
}
}
能够看出,咱们在visitReferenceExpression
回调中检测表达式,咱们不只需求判别是否存在Toast.makeTest
表达式,因为或许存在同名类,更需求判别Toast
类的详细类型,而这就需求获取语义信息
咱们这儿经过bindingContext
来获取表达式的语义,这儿的bindingContext
其实便是Kotlin
编译器存储语义信息的表,详细的能够参看:K2 编译器是什么?国际第二高峰又是哪座?
当咱们获取了语义信息之后,就能够获取Toast
的详细类型,就能够判别出这个Toast
是不是android.widget.Toast
,也就能够完结检测了
Github Action
集成Detekt
检测
在完结了DeteKt
接入与自界说规矩之后,接下来便是每次提交代码时在CI
进步行检测了
一些大的开源项目每次提交PR
都会进行一系列的检测,咱们也用Github Action
来完结一个
咱们在.github/workflows
目录增加如下代码
name: Android CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
detekt-code-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
cache: gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: DeteKt Code Check
run: ./gradlew detektDebug
这样在每次提交PR
的时分,就都会主动调用该workflow
进行检测了,检测不经过则不允许兼并,如下所示:
点进去也能够看到详细的报错,详细是哪一行代码检测不经过,如图所示:
总结
本文首要介绍了DeteKt
的接入与如何自界说规矩,经过IDE
集成,CLI
命令行办法与Gradle
插件办法接入,以及CI
主动检测,能够确保代码标准,IDE
提示,CI
检测三者的一致,便利提早暴露问题,提高代码质量。
假如本文对你有所协助,欢迎点赞~
示例代码
本文所有代码可见:github.com/RicardoJian…
参看资料
detekt.dev/docs/intro
代码质量堪忧?用 detekt 呀,拿捏得死死的~