⚠️本文为稀土技术社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!

前语

各个团队多少都有一些自己的代码标准,但拟定代码标准简单,困难的是如何落地。假如彻底依靠人力Code Review难免有所遗漏。

这个时分就需求经过静态代码查看东西在每次提交代码时主动查看,本文首要介绍如何运用DeteKt落地Kotlin代码标准,首要包含以下内容

  1. 为什么运用DeteKt?
  2. IDE接入DeteKt插件
  3. CLI命令行办法接入DeteKt
  4. Gradle办法接入DeteKt
  5. 自界说Detekt检测规矩
  6. Github Action集成Detekt检测

为什么运用DeteKt?

说起静态代码查看,咱们首先想起来的或许是lint,比较DeteKt只支撑Kotlin代码,lint不只支撑KotlinJava代码,也支撑资源文件标准查看,那么咱们为什么不运用Lint呢?

在我看来,Lint在运用上首要有两个问题:

  1. IDE集成不够好,自界说lint规矩的正告只要在运行./gradlew lint后才会在IDE上展现出来,在clean之后又会消失
  2. lint查看速度较慢,尤其是大型项目,只对增量代码进行查看的逻辑需求自界说

DeteKt供给了IDE插件,敞开后可直接在IDE中查看正告,这样能够在第一时间发现问题,防止后续查看发现问题后再修正流程过长的问题

一起Detekt支撑CLI命令行办法接入与Gradle办法接入,支撑只查看新增代码,在查看速度上比起lint也有一定的优势

IDE接入DeteKt插件

假如能在IDE中提示代码中存在的问题,应该是最快发现问题的办法,DeteKt也交心的为咱们预备了插件,如下所示:

落地 Kotlin 代码规范,DeteKt 了解一下~

首要能够装备以下内容:

  1. DeteKt开关
  2. 格式化开关,DeteKt直接运用了ktlint的规矩
  3. Configuration file:规矩装备文件,能够在其间装备各种规矩的开关与参数,默许装备可见:default-detekt-config.yml
  4. Baseline file:基线文件,越过旧代码问题,有了这个基线文件,下次扫描时,就会绕过文件中列出的基线问题,而只提示新增问题。
  5. Plugin jar: 自界说规矩jar包,在自界说规矩后打出jar包,在扫描时就能够运用自界说规矩了

DeteKt IDE插件能够实时提示问题(包含自界说规矩),如下图所示,咱们增加了自界说制止运用kae的规矩:

落地 Kotlin 代码规范,DeteKt 了解一下~

对于一些支撑主动修复的格式问题,DeteKt插件支撑主动格式化,一起也能够装备快捷键,一键主动格式化,如下所示:

落地 Kotlin 代码规范,DeteKt 了解一下~

CLI命令行办法接入DeteKt

DeteKt支撑经过CLI命令行办法接入,支撑只检测几个文件,比方本次commit提交的文件

咱们能够经过如下办法,下载DeteKtjar然后运用

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$ # 需求扫描的源文件,多个途径之间用,或许;衔接

经过如上办法进行代码查看速度是非常快的,依据经验来说一般便是几秒之内能够完结,因此咱们完结能够将DeteKtgit hook结合起来,在每次提交commit的时分进行检测,而假如是一些比较耗时的东西比方lint,应该是做不到这一点的

类型解析

上面咱们提到了,DeteKt--classpth参数与--language-version参数,这些是用于类型解析的。

类型解析是DeteKt的一项功用,它允许 Detekt 对您的 Kotlin 源代码执行更高档的静态剖析。

一般,Detekt 在编译期间无法拜访编译器语义剖析的成果,咱们只能获取Kotlin源代码的笼统语法树,却无法知道语法树上符号的语义,这约束了咱们的查看能力,比方咱们无法判别符号的类型,两个符号终究是不是同一个对象等

经过启用类型解析,Detekt 能够获取Kotlin编译器语义剖析的成果,这让咱们能够自界说一些更高档的查看。

而要获取类型与语义,当然要传入依靠的class,也便是classpath,比方android项目中常常需求传入android.jarkotlin-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/**") // 过滤指定目录
}

如上所示,接入首要需求做这么几件事:

  1. 引进插件
  2. 装备插件,首要是装备configbaseline,即规矩开关与老代码过滤
  3. 引进detekt-formatting与自界说规矩的依靠
  4. 装备JvmTarget,用于类型解析,但不必再装备classpath了。
  5. 除了baseline之外,也能够经过includeexclude的办法指定只扫描指定文件的办法来完结增量检测

经过以上办法就接入成功了,运行./gradlew detektDebug就能够开始检测了,扫描成果可在终端直接查看,并能够直接定位到问题代码处,也能够在build/reprots/途径下查看输出的陈述文件:

落地 Kotlin 代码规范,DeteKt 了解一下~

自界说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"
                )
            )
        }
    }
}

代码其实并不复杂,首要做了这么几件事:

  1. 增加CustomRuleSetProvider作为自界说规矩的进口,并将NoSyntheticImportRule增加进去
  2. 完结NoSyntheticImportRule类,首要包含issue与各种visitXXX办法
  3. issue属性用于界说在控制台或任何其他输出格式上打印的ID、严重性和提示信息
  4. 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进行检测了,检测不经过则不允许兼并,如下所示:

落地 Kotlin 代码规范,DeteKt 了解一下~

点进去也能够看到详细的报错,详细是哪一行代码检测不经过,如图所示:

落地 Kotlin 代码规范,DeteKt 了解一下~

总结

本文首要介绍了DeteKt的接入与如何自界说规矩,经过IDE集成,CLI命令行办法与Gradle插件办法接入,以及CI主动检测,能够确保代码标准,IDE提示,CI检测三者的一致,便利提早暴露问题,提高代码质量。

假如本文对你有所协助,欢迎点赞~

示例代码

本文所有代码可见:github.com/RicardoJian…

参看资料

detekt.dev/docs/intro
代码质量堪忧?用 detekt 呀,拿捏得死死的~