vlambda博客
学习文章列表

代码质量堪忧?用 detekt 呀,拿捏得死死的~


930改革之后,我们公司内部的技术基建的发展有了显著的成效,对于代码质量的追求也成为工程师们日常生活中最重要的几件事之一。对于我们 Kotlin 开发者而言,代码的静态扫描其实意义非凡,除了让公司项目代码风格保持统一以外,也能够纠正我们日常开发当中的不当写法,对于提升我们的编码意识有很好的效果。所以,我特别邀请我们组内的小伙伴撰写了这篇关于 detekt 的上手指南,希望对提升大家的代码质量有帮助。


另外,我们现在在招 Android 暑期实习生(腾讯地图,工作地:腾讯北京总部大厦),我们面试一向只看真本领,不面八股文,欢迎有兴趣的小伙伴投递简历至:[email protected]

代码质量堪忧?用 detekt 呀,拿捏得死死的~


本文介绍了 Kotlin 代码扫描工具 Detekt 的基本使用方法,并在此基础上总结了一套 Kotlin 代码规范治理技巧。

引言

最近,团队在进行代码规范的治理,趁这个机会,调研了一下 Kotlin 语言的代码扫描工具的使用,摸索出了一套针对 Kotlin 语言的代码规范治理方案。

想要提升项目的代码质量,除了在团队中推行 CodeReview 以外,更多地还是要依赖静态代码分析工具,来自动化地完成代码规范检查和整改。

类似于 Java 语言的 checkstyle 工具,Kotlin 也有两个类似的静态代码分析工具:

  • ktlint [1]:Kotlin linter 工具,可自动格式化代码。默认规则集扫描的都是代码格式问题。
  • detekt [2]:同样是针对 Kotlin 语言的静态代码分析工具,除了代码格式问题(集成了 Ktlint 的功能),还能扫描出代码风格问题和潜在风险。

鉴于 detekt 涵盖了 Ktlint 的功能,因此直接选用 detekt 来作为代码扫描工具。

借助 detekt 治理 Kotlin 代码

配置 detekt

使用 detekt 的第一步是在 Gradle 工程中引入 detekt 插件。

接入 detekt

正如官方文档[3]中所介绍的,在 gradle 工程中使用 detekt,只需3个步骤。

Step1:在工程根目录下的 build.gradle 文件中,引入 detekt gradle plugin:

// root build.gradle
buildscript {
    repositories {
        jcenter()
    }
}

// 引入 detekt gradle plugin 插件,并指定版本
plugins {
    id("io.gitlab.arturbosch.detekt").version("1.16.0-RC1")
}

repositories {
    jcenter()
}

Step2: 对 detekt 进行配置:

// root build.gradle
detekt {
  input = files("src/main/kotlin""src/main/java"// 指定需要扫描的源代码文件路径
  config = files("config/detekt.yml"// 指定采用的规则集文件
  reports { // 指定输出的报告文件类型
  html {
      enabled = true                        // Enable/Disable HTML report (default: true)
      destination = file("build/reports/detekt.html"// Path where HTML report will be stored (default:
    }
  }
}

可配置的属性包括:指定输入的源代码文件,采用的规则集文件,输出的报告文件等。更多配置信息可以参看Options for detekt configuration closure[4]

Step3:运行./gradlew detekt命令即可。扫描结果即可在终端直接查看,并可以直接定位到问题代码处:





代码质量堪忧?用 detekt 呀,拿捏得死死的~

也可以在build/reprots/路径下查看输出的报告文件:

代码质量堪忧?用 detekt 呀,拿捏得死死的~

在子模块中应用 detekt

对于包含多个子模块的工程来说,如果想要分模块对代码进行扫描,也可以在 subprojects 闭包引入 detekt 插件,这样就可以按需扫描不同的模块,而不用每次都扫描全部代码。

Step1:配置的时候,需要在子模块中引入 detekt 插件并配置:

// root build.gradle
buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.0.1'  // 注意 AGP 版本的兼容性
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10"
        classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.15.0" // 在这里添加 Detekt 依赖
    }
}

// 在这里为每个模块引入 Detekt 扫描
subprojects {
    apply plugin: "io.gitlab.arturbosch.detekt"
    tasks.detekt.jvmTarget="1.8"
    detekt {
        config=files("$rootDir/config/detekt/detekt_config.yml")
        reports {
            html.enabled=true
        }
    }
}

注意,这里存在一个坑,最新的 detekt 1.16.0-RC1 只兼容 AGP4.1+,如果使用更早版本的 AGP,在 subproject 中引用 detekt 会报错,详见 issue[5]

Step2:运行 ./gradlew detekt 会分模块扫描,也可以运行 ./gradlew app:detekt 只扫描 app 模块这个模块下的代码。结果报告也会在对应模块的 build 路径下生成。

制定规则集

在进行代码规范整改之前,还需要制定规范规则,这样才能有的放矢地进行代码整改。

如果没有在 detekt 闭包中指定 config 属性,detekt 会使用默认的规则。这些规则采用 yaml 文件描述,运行 ./gradlew detektGenerateConfig 会生成 config/detekt/detekt.yml 文件,我们可以在这个文件的基础上制定代码规范准则。

detekt.yml 中的每条规则形如:

complexity: # 大类
  active: true
  ComplexCondition: # 规则名
    active: true  # 是否启用
    threshold: 4  # 有些规则,可以设定一个阈值
# ...

更多关于配置文件的修改方式,请参阅官方文档-配置文件[6]

detekt 的规则集划分为 9 个大类,每个大类下有具体的规则:

规则大类 说明
comments 与注释、文档有关的规范检查
complexity 检查代码复杂度,复杂度过高的代码不利于维护
coroutines 与协程有关的规范检查
empty-blocks 空代码块检查,空代码应该尽量避免
exceptions 与异常抛出和捕获有关的规范检查
formatting 格式化问题,detekt直接引用的 ktlint 的格式化规则集
naming 类名、变量命名相关的规范检查
performance 检查潜在的性能问题
potentail-bugs 检查潜在的BUG
style 统一团队的代码风格,也包括一些由 Detekt 定义的格式化问题

更细节的规则说明,请参阅规则集说明[7]

代码整改小技巧

在代码整改过程中,借助 detekt 提供的诸多功能,可以极大地提升效率。

自动格式化代码

对于 formatting 类别下的规则,都包含有 autoCorrect 这个属性选项,这表示是否需要在扫描代码的同时,自动对代码执行格式化。这个功能可以极大地方便大家整改与格式有关的代码问题。

要启用这个功能,还需要引入 formatting 插件,它是 detekt 提供的插件,打包了 ktlint 的功能,使用时配置如下:

detekt {
  // ...
  autoCorrect = true // 自动格式化代码的总开关
}

dependencies {
    detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:[version]"
}

在实际使用的时候,如果开启自动格式化的功能,运行时可能会报IndexOutOfBoundsException[8]的错误,原因是格式化时会修改原文件,导致执行其它规则的扫描时产生异常,一种解决办法是,另外配置一个专门用于执行格式化的task,它所指定的规则集只开启 format 类别的规则:

// root build.gradle
subprojects {
    apply plugin: "io.gitlab.arturbosch.detekt"

    dependencies {
        detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:$detekt_version"
    }

   // 专门用于 format 的task
    task detektFormat(type: io.gitlab.arturbosch.detekt.Detekt) {
        description = "Reformat kotlin code."
        config.setFrom(files("$rootDir/config/detekt/detekt_${detekt_version}_format.yml"))
        setSource(files("$projectDir/src/main"))
        autoCorrect = true
        include("**/*.kt")
    }
}

对单个文件执行检查

detekt 除了可以在 gradle 中配置使用,还可以通过 CLI 命令行的方式使用。下载 detekt-cli-1.15.0-all.jar[9] 就可以通过运行 java -jar 的方式在终端中运行Detekt。其它版本可以从此下载[10]。如果还需要扫描格式化相关规则,还需要引入 detekt-formatting-1.15.0.jar[11]

基于这个特性,我们可以在 AS 中配置External Toos,就可以在对编辑器当前打开的文件执行 detekt 扫描,这样可以方便我们在整改代码时,实时查看整改进度,而不用每次执行全局扫描,有助于提升整改代码的效率。

Step1:配置(Preferences > Tools > External Toos)

代码质量堪忧?用 detekt 呀,拿捏得死死的~

其中的 CLI 参数配置配置如下

-jar /path/to/detekt-cli-1.15.0-all.jar # detekt-cli-1.15.0-all.jar所在路径
-c /path/to/detekt_1.15.0_format.yml # 规则文件所在路径
--plugins /path/to/detekt-formatting-1.15.0.jar # detekt-cli-1.15.0-all.jar所在路径
-ac # 开启自动格式化
-i $FilePath# 需要扫描的源文件,这里直接使用 AS 提供的 $FilePath$,表示当前编辑器所打开的文件

Step2:在已打开的文件空白处,点击右键菜单 > External Tools > detekt_formater即可运行,结果会在控制台中输出。

代码质量堪忧?用 detekt 呀,拿捏得死死的~

绕过检查

通过配置文件可以开启或关闭某些规则,但如果仅仅想忽略某些文件中某些问题,而不是直接关闭这类问题的扫描,detekt 提供了两种方式来绕过(原文是 suppress issue,直译成压制问题,意译的话,可以解释为成绕过检查)。

第一种方式是使用 @Suppress 注解[12]:

  • 在类名或者方法名前添加诸如 @Suppress("LargeClass") 这样的注解,可以有针对性地忽略这个文件中的某些告警。

  • 在文件头添加 @file:Suppress("TooManyFunctions"),则可以直接告诉 detekt 不扫描这个文件中的这类问题。

第二种方式是通过基线文件[13],好处是不会因为添加了 @Suppress 注解而污染原代码。另一方面,对于臃肿的历史代码,可能没有时间来完全重构,如果直接添加 @Suppress 绕过整个文件,那么如果下次修改了这个文件,引入的新问题可能也会被绕过。

这种场景,就可以运行 ./gradew detektBaseline 来生成一个 baseline.xml 基线文件。有了这个基线文件,下次扫描时,就会绕过文件中列出的基线问题,而只提示新增问题。

高亮问题

detekt还提供了 IntelliJ 插件[14],简单配置后,就可以在编辑器中高亮显示,代码中存在问题。便于我们在代码编写的过程中,留意存在的代码问题。

高亮的效果和 IDE 自带的 inspection 的代码提示效果类似,鼠标移动到高亮的地方后会显示问题提示。

小结

detekt 为我们提供了一套完整的 Kotlin 代码规范治理方案:

  • 在工程中引入 detekt 工具,可以很方便的对 Kotlin 代码执行静态扫描
  • 对 detekt 提供的规则集进行修改,可以定义适合团队的代码规范
  • 使用 detekt 提供的 autoCorrect 特性,可以快速地格式化代码
  • 使用 detekt 提供的 CLI 接口,可以在 IDE 中对单个文件执行检查,实时反馈问题整改进度
  • 可以通过 @Suppress 注解或者基线文件,来绕过检查
  • 安装 detekt 的 IntelliJ 插件,可以在代码编写的过程中高亮提醒存在的代码问题

总结

整改老代码是个费时费力的活,本文所讨论的 detekt 的使用方法,可以一定程度上提升整改的效率。此外,借助 detekt 这类静态代码扫描工具,也可以在平时开发的过程中,时刻反馈代码质量问题,避免潜在的代码缺陷。

这次也是借着“代码质量治理”这股东风,对项目历史代码进行了整改。但是,整改不仅仅是为了提高质量得分,更重要的是要提高代码规范的意识,在平时的开发过程中就注重写出规范的代码,如此,才能使得软件的质量得到更加充分的保障。

参考资料

[1]

ktlint: https://ktlint.github.io/

[2]

detekt: https://github.com/detekt/detekt

[3]

官方文档: https://detekt.github.io/detekt/index.html

[4]

Options for detekt configuration closure: https://detekt.github.io/detekt/gradle.html#options-for-detekt-configuration-closure

[5]

issue: https://github.com/detekt/detekt/issues/3476

[6]

官方文档-配置文件: https://detekt.github.io/detekt/configurations.html

[7]

规则集说明: https://detekt.github.io/detekt/comments.html

[8]

IndexOutOfBoundsException: https://github.com/detekt/detekt/pull/3317

[9]

detekt-cli-1.15.0-all.jar: https://github.com/detekt/detekt/releases/download/v1.15.0/detekt-cli-1.15.0-all.jar

[10]

从此下载: https://github.com/detekt/detekt/releases

[11]

detekt-formatting-1.15.0.jar: https://github.com/detekt/detekt/releases/download/v1.15.0/detekt-formatting-1.15.0.jar

[12]

使用 @Suppress 注解: https://detekt.github.io/detekt/suppressing-rules.html

[13]

通过基线文件: https://detekt.github.io/detekt/baseline.html

[14]

IntelliJ 插件: https://github.com/detekt/detekt-intellij-plugin