为什么Kotlin代码审查不是关于“我会用不同的方式写”?
\n\nCode review(或代码审查)是任何值得尊敬的团队中的神圣仪式。但当谈到Kotlin时,许多开发者会陷入困惑。“这里的一切都如此简洁!”,“嗯,这是一种基于表达式的语言”——您一边滚动拉取请求一边想。但您错了。
\n\nKotlin很漂亮,但也暗藏玄机。它为您提供了强大的工具:作用域函数、密封类、协程。同时,也提供了同样多的机会让您搬起石头砸自己的脚。在本文中,我们将分析在Kotlin代码审查中真正需要注意什么,以确保您的代码不会变成带有可空副作用的“意大利面条式怪物”。
\n\n本文不是语法教程。这是一份供审查者使用的实用检查清单,他们希望看到的不仅仅是能工作的代码,而是可维护且安全的产品。
\n\n1. 空安全:当“?”不是万能药时
\n\nKotlin以其空安全为傲。这是正确的。但在代码审查中,您必须检查的不仅仅是“!!”的存在,而是与可空类型相关的逻辑。
\n\n什么绝对应该引起警惕?
\n\n- \n
- 无节制地使用“!!”。如果开发者使用了
!!,他们应该在注释中解释为什么这个值保证不为null。如果没有解释,那就是一个bug。 \n - 安全调用(?.)和Elvis操作符(?:)的链式使用。它们常常掩盖逻辑错误。例如: \n
// 问题:如果 user?.address?.city == null,\n// 我们会默默地返回“Unknown”而不记录日志\nval city = user?.address?.city ?: "Unknown"\n\n\n应该怎么做? 如果null是异常情况,请使用requireNotNull或checkNotNull。如果这是正常行为,请记录日志。
val city = user?.address?.city\n ?: run {\n logger.warn("User $userId has no city")\n "Unknown"\n }\n\n\n在kotlin代码审查中,始终要问:“如果这个null是一个我们现在忽略了的bug,该怎么办?”
\n\n2. 作用域函数:明智地使用let、apply、run
\n\n作用域函数是Kotlin的强大之处。但在实践中,我看到它们被当作锤子来敲任何钉子。Kotlin代码检查应该包括分析:“为什么这里用let而不是run?”
典型的陷阱:
\n\n- \n
- 使用let进行非空检查。可以,但不要将它们嵌套在一起: \n
// 糟糕\nuser?.let { u ->\n address?.let { a ->\n process(u, a)\n }\n}\n\n// 良好\nif (user != null && address != null) {\n process(user, address)\n}\n\n\n- \n
- 使用apply进行对象修改。这是它的天然用途。但如果您在
let内部使用apply——打住。这闻起来像意大利面条式代码。 \n
请记住:with用于分组调用,also用于副作用(日志记录),run用于在上下文中进行计算。不要混淆。
3. 不可变性:var是危险信号
\n\nKotlin提倡使用val。这很好。但在Kotlin代码审查中,我经常看到开发者在可以使用val加复制的地方写var。
// 不好\nvar result = mutableListOf<Int>()\nitems.forEach { result.add(it * 2) }\n\n// 良好\nval result = items.map { it * 2 }\n\n\n规则很简单:如果您看到var,就问:“为什么不用val?”大多数情况下,答案会让您惊讶:“我没想过。”
唯一的例外是当确实需要修改时(例如,在累加循环中)。但即使在那里,也常常可以用fold或reduce替换。
4. 密封类和when:不要忘记else
\n\n密封类是优雅的替代