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 构建将会失败。
类型和计算安全
该工具禁止直接通过 == 比较 Float 和 Double 类型。由于浮点数的二进制表示特性,这种比较经常导致难以捕获的 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 集成

该工具可以输出 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。
相关项目