第116篇 Kotlin 代码评审清单:团队规范与静态检查

简介: 本节聚焦Kotlin团队代码质量保障机制,提出“三层金字塔”模型:①工具层(ktlint/detekt/编译器)自动化查格式与确定性缺陷;②约定层(PR模板/基线/baseline)半强制落地规范;③设计层(架构/抽象/命名)依赖人工判断。核心是“机器管规则,人管设计”,避免告警泛滥与评审失焦。

前面几节讲的是"怎么写",这一节讲"怎么保证团队写出来的代码跟你一样好"。这题的面试形态很特别——它不考语法也不考 API,考的是你有没有一套能落地的质量保障机制。答得浅的人说"团队 review 很严格",答得深的人会讲清"哪些交给机器、哪些必须留给人,以及告警怎么管"。

先把结论放在前面:Kotlin 代码质量保障分三层,越靠下越应该自动化,越靠上越需要人的判断。①工具层:ktlint(格式)、detekt(潜在缺陷)、Kotlin 编译器警告——机器管风格与确定性缺陷;②约定层:团队规范文档 + PR 模板 + 关键检查项——半强制;③设计层:架构边界、抽象选择、命名语义——只能靠人。核心原则是:把机器能查的规则化,人的 review 时间才腾得出来看设计。这一节的判断力就体现在"知道哪条规则该放进哪一层"。

机制背后的执行路径

先说清三个工具各自负责什么,避免职责重叠导致告警重复:

| 工具 | 负责 | 典型规则 | |---|---|---| | ktlint | 代码格式 | 缩进、换行、import 顺序、尾随空格 | | detekt | 潜在缺陷与复杂度 | NestedBlockDepth、LongMethod、MagicNumber、UnsafeCallOnNullableType | | 编译器警告 | 语言级风险 | 未使用变量、可疑的 when 不完整、废弃 API |

关键是不重叠。如果 ktlint 管缩进、detekt 也管缩进,同一个问题会报两次,告警立刻变得无法阅读——这是很多团队上工具失败的直接原因。

然后是 detekt 的规则分级。error 级别应当让构建失败(真正会出 bug 的),warning 级别只报告,info 级别可选。举几个在 Android 项目里真正有用的规则:

// detekt 配置片段
buildUponDefaultConfig = true
config.setFrom("$dir/config/detekt.yml")

complexity:
  active: true
  NestedBlockDepth:
    active: true
    threshold: 3          # 缩进超过 3 层即报
  LongMethod:
    active: true
    threshold: 40         # 单函数超 40 行即报
  LongParameterList:
    active: true
    functionThreshold: 6
  TooManyFunctions:
    active: true
    thresholdInClasses: 12

exceptions:
  TooGenericExceptionCaught:
    active: true          # 禁 catch(Exception) 后不处理
  SwallowedException:
    active: true          # 禁吞异常
  PrintStackTrace:
    active: true

style:
  MagicNumber:
    active: true
    ignoreNumbers: ['-1', '0', '1', '2']
  ReturnCount:
    active: true
    max: 6                # 卫语句会提高 return 数,阈值要放宽
  UnusedPrivateMember:
    active: true

注意 ReturnCount 这一条——第 115 篇刚讲了卫语句会天然增加 return 数量。规则阈值要匹配你想推行的编码风格,否则团队会被迫关掉规则。这是"判断力"的具体体现。

真实工程场景的推演

场景一:上线前把告警清零的节奏。真实做法是分三步:①先跑一次全量报告,把现有告警分类——改(真 bug)/ 配(规则不适配)/ 延(历史债);②把"改"里高危的先修;③对"配"的调低级别或关掉,对"延"的用 baseline 文件记录当前值,新增告警才算失败。这个 baseline 机制是让老项目也能上 CI 的关键。

场景二:baseline 的正确用法。detekt 支持 baseline.xml:

# 首次:生成基线
./gradlew detektBaseline   # 生成 config/detekt/baseline.xml

# CI:新增告警即失败(baseline 里没有的会被拦下)
./gradlew detekt -PdetektBaseline=config/detekt/baseline.xml

这解决了"存量代码有问题、想上 CI 又不能一次改完"的困境,是工程落地的关键一步。

场景三:PR 模板里的检查项。工具管不到的判断,需要写进模板让人工确认。有效的 PR 模板项是可回答的问题,不是笼统提醒:

## 变更说明
- [ ] 本次改动的影响范围(模块 / 页面 / 接口)
- [ ] 是否涉及数据库或 SP 结构变更(是 → 补充迁移方案)
- [ ] 是否新增了第三方依赖(是 → 补充体积与合规评估)

## 自检
- [ ] 协程:取消是否正确传递?有无 GlobalScope?
- [ ] 空安全:新增的 Java 互操作是否做了归一化?是否用了 !!
- [ ] 状态:UI 状态是否用 sealed?StateFlow 与事件是否分离?
- [ ] 生命周期:是否用 viewLifecycleOwner?收集是否包 repeatOnLifecycle?
- [ ] 性能:是否在主线程做 IO?列表是否给 key?

场景四:告警治理的真实教训。团队常见失败模式是"上工具 → 告警几百条 → 全局压制 → 工具形同虚设"。避免的关键是从第一天就分级 + baseline,而不是"先全开看看"。

最常见的坑是

只上工具不立规范,告警堆积后被整体压制形同虚设。 表现是 @Suppress 铺满代码、detekt 变成"摆设"。修法:①分级(error 才阻断构建);②对历史债用 baseline;③每条抑制都要写注释说明理由,review 时抽查。

其次是规则与团队风格冲突(典型是 ReturnCount 与卫语句、LongParameterList 与数据类)。修法:调阈值或关掉该条,而不是让成员到处加 @Suppress——后者是在对抗工具,最终会失去约束力。

还有一个更隐蔽的坑:把设计层问题塞进静态检查。试图用规则限制"不能滥用继承"这类判断,结果规则要么太宽形同虚设、要么太窄误报。修法:设计层靠架构文档 + PR 模板里的判断题 + 少数人的 review 质量,不靠工具。

现场手写这一段就够了

// 1) 自定义 detekt 规则:表达团队特有的约定
class NoGlobalScopeRule(config: Config) : Rule(config) {
   
    @Configuration("是否禁止 GlobalScope")
    @Configuration("允许的包名前缀白名单")
    private val whitelist: String = ""

    override fun visitKtFile(file: KtFile) {
   
        file.collectDescendantsOfType<KtDotQualifiedExpression>().forEach {
    expr ->
            val txt = expr.text
            if (txt.startsWith("GlobalScope") && !txt.startsWith(whitelist)) {
   
                report(CodeSmell(issue = "禁止 GlobalScope:任务没有归属,页面退出后仍会运行",
                    entity = expr, priority = Priority.HIGH))
            }
        }
    }
}
# config/detekt.yml
complexity:
  NestedBlockDepth:
    active: true
    threshold: 3
  LongMethod:
    active: true
    threshold: 40
exceptions:
  SwallowedException:
    active: true
  TooGenericExceptionCaught:
    active: true
  PrintStackTrace:
    active: true
style:
  MagicNumber:
    active: true
    ignoreNumbers: ['-1','0','1','2']
  ReturnCount:
    active: true
    max: 6            # 卫语句风格需要放宽
  UnusedPrivateMember:
    active: true
// 2) 自我约束的写法:让规则意图体现在类型上
class CoroutineScopeHolder {
                    // 名字即约束:它必须被 cancel
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    fun scope(): CoroutineScope = scope
    fun close() = scope.cancel()
}
// 3) 机器查不到、需要人判断的部分:写成单测与文档
// 单测是"可执行的规范"
@Test fun `event stream must not replay`() = runTest {
   
    val vm = LoginViewModel()
    vm.events.emit(ShowError("旧错误"))            // 订阅前发射
    val received = mutableListOf<LoginEvent>()
    val job = launch(UnconfinedTestDispatcher()) {
    vm.events.collect {
    received += it } }
    advanceUntilIdle()
    assertTrue(received.isEmpty())                  // 断言:不应收到旧事件
    job.cancel()
}

关键行解读:自定义 detekt 规则能把"禁止 GlobalScope"这类团队约定变成机器检查;detekt.yml 的阈值要与编码风格匹配(ReturnCount 放宽是卫语句风格的必要让步);把 scope 包装成 Closeable 形态,让"必须取消"变成类型与命名上的提示;可执行的单测是最可靠的"规范文档"。

面试追问四连

"detekt 和 ktlint 怎么分工?" 答:ktlint 管格式(缩进、换行、import 顺序),detekt 管潜在缺陷与复杂度(嵌套深度、方法长度、吞异常、魔法数字)。原则是不重叠——同一问题报两次会让告警失去可读性。

"老项目代码有问题,怎么上 CI?" 答:先生成 baseline 记录存量告警,CI 只拦截"新增"告警;再分批清理存量(按优先级:安全 > 正确性 > 风格)。工具的价值是防止劣化,不必强求一次清零。

"哪些规则不能交给静态检查?" 答:设计层判断——抽象是否合理、模块边界是否恰当、命名是否表达业务语义、错误处理策略是否符合业务预期。这类问题需要人理解意图,静态检查只能拦住语法层。

"怎么防止 review 变成挑格式?" 答:格式与确定性缺陷统一交给工具,PR 模板只保留需要判断的问题(影响范围、架构边界、风险预案),让人把时间花在设计而非空格上。

落地建议

1. 落地顺序:先 ktlint(纯格式、零争议)→ 再开 detekt 的确定性规则(吞异常、未使用成员)→ 最后开复杂度规则。每步都要分级 + baseline。 2. 写一份规则与理由对照表(为什么开、为什么调阈值、哪些关掉),让后来人能理解每个决策,而不是只能看到一份"看起来很严"的配置。 3. PR 模板只保留可回答的判断题(影响范围、数据迁移、协程/空安全/生命周期三类高危项),格式类不写进模板。 4. 自动化预防:把每条 @Suppress 都要求带注释说明理由,用脚本定期抽查无理由的抑制;CI 上跑 ktlint 检查、detekt 检查、baseline 差异对比,三者任一失败即阻断合并。

给正在准备面试的你 把这题画成"三层质量保障金字塔"。最底层"工具层"(ktlint 格式 / detekt 缺陷 / 编译器警告),标"确定性规则,机器管,CI 阻断";中间层"约定层"(团队规范文档 / PR 模板 / baseline 基线),标"半强制,靠流程与抽查";最上层"设计层"(架构边界 / 抽象选择 / 命名语义),标"只能靠人,工具无效"。图右侧竖排写一条箭头标"人力投入方向"——越往上人花的时间越多,工具的意义是把下两层的人力解放到上层。

再补一个高分追问答案:"怎么让代码评审真正有质量,而不是走过场?" 四条实操:①限制 PR 体积(超过 400 行建议拆分),大 PR 的评审质量会急剧下降;②模板只问需要判断的问题,格式类交给工具,评审者不必逐行抠空格;③分级评审——核心模块(支付、登录、数据层)由指定人评审,普通模块同组互评;④评审意见分类——blocker(必须改)/ suggestion(建议)/ nit(吹毛求疵)分开标注,作者可以只回应 blocker,避免讨论被细节淹没。答出"怎么让人把时间花在设计上",就是这题的核心。

复习时别孤立刷题:卫语句与早返回——上一节讲单条编码纪律,本节讲如何把这类纪律从"个人习惯"变成"团队可执行的标准",是个人能力到工程化的跨越。


如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。你的支持就是这个系列持续更新的动力。

「Android软件开发面试·从入门到精通」连载系列

上一篇:卫语句与早返回:可读性重构实战

下一篇预告:Kotlin-阶段面试通关地图:高频考点串联复盘

有任何问题欢迎在评论区留言交流。

相关文章
|
18天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
8618 25
|
16天前
|
人工智能 并行计算 PyTorch
秋叶 ComfyUI 2026 整合包 v3.2 完整部署教程:Python 3.13 + Torch 2.13 全栈升级
秋叶aaaki ComfyUI 2026年8月整合包v3.2正式发布!全面升级Python 3.13.11、PyTorch 2.13.0+cu130及ComfyUI v0.30.2,原生支持MiniMax H3、Wan 2.2、Qwen-Image-2.1等2026主流音视频/图像模型,解压即用,无需环境配置。
3040 14
|
16天前
|
人工智能 测试技术 API
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
Jev是TypeSafe AI推出的“系统一模型”,不生成文本,专做毫秒级结构化决策:Choice(多选)、Score(打分)、Noul(是非概率)。响应快193倍、成本低444倍,适合工单路由、内容审核、测试定级等高频判断场景。
2110 4
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
|
5天前
|
人工智能 JSON Linux
【全网最详细】ComfyUI使用教程:下载+本地部署+配置+工作流搭建一篇搞定(2026最新版)
ComfyUI是一款免费开源的本地AI绘图工具,采用节点式工作流设计,支持文生图、图生图、局部重绘、放大、换脸等多种功能。可离线运行,依赖显卡加速,无需联网。支持自定义流程保存与分享,插件生态丰富,适合进阶用户。(239字)
|
16天前
|
云安全 人工智能 安全
|
11天前
|
人工智能 Linux 开发者
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
Codex是OpenAI推出的AI编程智能体,可读取本地项目、理解需求并自动修改代码。支持桌面GUI、命令行(CLI)及VS Code/Cursor插件三种形态,覆盖可视化操作、终端高效开发与编辑器无缝集成场景,助开发者用自然语言驱动编码全流程。(239字)
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
|
11天前
|
人工智能 JSON 编解码
【2026最新版】ComfyUI本地部署教程,新手也能看懂!
ComfyUI是本地运行的AI绘画工具,采用节点式工作流设计:通过拖拽连接“加载模型”“提示词编码”“采样”“解码”等模块,实现高度可控的文生图。新手推荐使用秋叶整合包,一键启动、内置模型管理与插件安装器,轻松上手。(239字)

热门文章

最新文章