>_ DevTrendszh

语言

首页

语言

板块

前端 后端 移动端 DevOps AI / ML 游戏开发 区块链 嵌入式 安全
Kotlin

diKTat 将 Kotlin 代码审查变成自动化流程

是否遇到过这样的情况:三分之一的 Pull Request 评论都是关于格式争议?有人留下了负布尔值如 isNoError,有人把类中方法的顺序搞混了,还有人用 == 运算符比较浮点数。从技术上讲,代码可以编译,测试也能通过,但六个月后再看这样的代码库简直让人头疼。

通常对于 Kotlin 项目,团队会采用 ktlint 和 detekt 的组合。前者关注缩进和间距,后者检查明显的代码异味。但在这两者之间存在一个灰色地带——架构风格和代码规范方面,团队要么自己编写规则,要么在手动审查上浪费时间。SaveOurTool 团队的 diKTat 仓库正好解决了这个问题。

diKTat 是什么

该项目是一套严格的 Kotlin 代码规范规则。从技术上讲,diKTat 构建于 ktlint 之上,并分析文件的 AST。该仓库包含一份详尽的指南,分为多个章节:命名规范、KDoc 注释、类结构、函数、类型和变量处理。

该工具包含一百多项检查规则,其中许多在其他静态分析工具中根本找不到。主要的便利之处在于 diKTat 不仅可以向控制台输出警告,还能自动修复发现的违规问题。

拯救代码的非典型检查

大多数 linter 都专注于代码格式。diKTat 挖掘得更深,能捕获语义上的异常。

命名和逻辑整洁性

diKTat 内置了对双重否定变量名的禁令。如果声明了一个标志 val isNotValid = false,linter 会要求将其重命名为肯定形式。当阅读时,像 !isNotValid 这样的结构会让人脑子一团乱麻,所以这条规则拯救了所有人的神经。

它还会检查带有否定语的函数调用。工具会坚持建议将 !list.isEmpty() 改为 list.isNotEmpty()

类成员排序

大型 Kotlin 文件中的一个常见问题是字段、函数和对象的混乱。diKTat 严格控制结构:

  • 编译时常量
  • 常规属性
  • late-init 属性
  • init 代码块(该工具禁止在无明确必要的情况下生成多个 init 代码块)
  • 构造函数
  • public、internal、protected 和 private 方法
  • Companion object

如果有人把私有辅助方法放在了公共 API 之前,CI 构建将会失败。

类型和计算安全

该工具禁止直接通过 == 比较 FloatDouble 类型。由于浮点数的二进制表示特性,这种比较经常导致难以捕获的 bug。diKTat 会强制你使用 abs(a - b) > EPS 进行 delta 比较,或切换到 BigDecimal

另一个不错的检查是追踪冗余的类型转换。如果 Kotlin 已经在条件内部执行了 Smart Cast,调用 as Type 将被标记为不必要的噪音。

// Было
if (x is String) {
    print((x as String).length)
}

// Стало после автофикса
if (x is String) {
    print(x.length)
}

如何运行和配置

diKTat 可以通过终端运行,集成到 Gradle 或 Maven 构建中,或通过 Spotless 聚合器连接。

添加到 Gradle

对于使用 Kotlin DSL 的 Gradle 项目,只需几行代码即可连接插件:

plugins {
    id("com.saveourtool.diktat") version "2.0.0"
}

diktat {
    inputs {
        include("src/**/*.kt")
        exclude("src/test/kotlin/excluded/**")
    }
    reporters {
        plain()
        html {
            output = file("build/reports/diktat.html")
        }
    }
}

使用命令 ./gradlew diktatCheck 运行检查,通过 ./gradlew diktatFix 可以自动修复分析器能触及的所有问题。

规则微调

配置位于标准 YAML 文件 diktat-analysis.yml 中。每条规则可以单独启用或禁用,许多规则还有特定参数:

name: HEADER_MISSING_OR_WRONG_COPYRIGHT
  enabled: true
  configuration:
    isCopyrightMandatory: true
    copyrightText: Copyright (c) MyTeam, 2024. All rights reserved.

name: HEADER_NOT_BEFORE_PACKAGE
  enabled: true
  ignoreAnnotated: [Generated, Controller]

如果需要在本地禁用特定检查,可以使用标准注解 @Suppress("FUNCTION_NAME_INCORRECT_CASE") 或通用的 @Suppress("diktat"),直接写在代码中即可。

通过 Baseline 渐进式采用

在没有准备的情况下,在有 50,000 行代码的旧项目上启用严格的 linter 是不可能的。开发人员会被成千上万的警告淹没。

为此,diKTat 提供了 baseline 模式。在首次运行时,该工具会生成一个包含项目中所有当前问题的 XML 文件:

./diktat --baseline=diktat-baseline.xml "src/**/*.kt"

baseline 文件会被提交到仓库。之后,linter 停止对旧代码的抱怨,只会在有人在新提交中引入新的违规问题时阻止构建。

GitHub Actions 集成

img.png

该工具可以输出 SARIF 格式的报告。结合 GitHub Actions,样式错误和警告会在 Pull Request 界面中直接高亮显示,并带有精确的行号引用。无需配置第三方机器人来发表评论。

name: Upload SARIF report
  uses: github/codeql-action/upload-sarif@v1
  if: always()
  with:
    sarif_file: build/reports/diktat/diktat.sarif

值得一试吗

diKTat 非常不妥协。它的指南要求明确的导入排序,限制函数长度为三十行,控制公共方法的 KDoc 文档存在性,并禁止不必要的 var

对于只有两个人的个人项目,这样的限制会显得过于严格。但如果一个分布式团队在开发一个服务,或者你在开发一个开源库,diKTat 消除了风格同步的麻烦,将代码审查时间解放出来用于讨论架构而非空白符。最简单的入门方式是添加 Gradle 插件并以单模块检查模式运行,同时生成一个 baseline。

相关项目