在 Kotlin 中重构遗留代码:策略与实践示例
处理遗留代码是每位开发者职业生涯中不可避免的一部分。通常,这类代码是用 Java 编写的,但在迁移到 Kotlin 时并未进行深入重构,或者表现为工具函数和可空字段的"意大利面条式代码"。重构此类代码不仅能提高可读性,还能提升应用性能与安全性。在本文中,我们将通过真实示例,分解在 Kotlin 中重构遗留代码的逐步策略。
1. 准备与测试:安全的基石
在修改代码之前,必须确保修改后代码的正确运行。没有测试,重构就变成了瞎猜。
- 编写表征测试:使用不同的输入数据运行现有代码,并记录输出值。这些测试将成为你的"安全网"。
- 使用 @Deprecated 注解:如果你无法立即移除旧的公共 API,请将其标记为
@Deprecated,并指明替代方案。 - 小步迭代:切勿一次性重构超过一个逻辑模块。每次提交都必须通过所有测试。
针对旧函数的表征测试示例:
// Legacy codefun calculateTotal(items: List<Int>?): Int { if (items == null) return 0 var sum = 0 for (item in items) { sum += item } return sum}
// Test@Testfun `calculateTotal returns sum or zero`() { assertEquals(0, calculateTotal(null)) assertEquals(6, calculateTotal(listOf(1, 2, 3))) assertEquals(0, calculateTotal(emptyList()))}2. 消除空安全并使用作用域函数
遗留代码常常因过多的空值检查而备受困扰。Kotlin 提供了优雅的工具来处理可空类型。
我们的做法:将嵌套的 if (x != null) 替换为 let、run 或 also。
// Before refactoringfun processUser(user: User?) { if (user != null) { if (user.name != null) { println("User: ${user.name}") } }}
// After refactoringfun processUser(user: User?) { user?.let { u -> u.name?.let { name -> println("User: $name") } }}
// Or even shorter using runfun processUserShort(user: User?) { user?.run { name?.let { println("User: $it") } }}这种方法使代码更具声明性,并减少了遗漏检查的可能性。
3. 用扩展函数替换工具类
在遗留的 Java 项目中,经常可以看到静态工具类(StringUtils、DateUtils)。在 Kotlin 中,最好用扩展函数来替代它们。
为什么这很重要:扩展函数使代码更易读,并且允许 IDE 将其作为类自身的方法进行提示。
// Legacy Java-styleobject StringUtils { fun isPalindrome(input: String): Boolean { val cleaned = input.filter { it.isLetterOrDigit() }.lowercase() return cleaned == cleaned.reversed() }}
// Usageval result = StringUtils.isPalindrome("A man, a plan, a canal: Panama")
// Refactoring to Kotlinfun String.isPalindrome(): Boolean { val cleaned = this.filter { it.isLetterOrDigit() }.lowercase() return cleaned == cleaned.reversed()}
// Usageval result = "A man, a plan, a canal: Panama".isPalindrome()同样值得关注的是,将 java.util.Date 替换为 java.time(LocalDate、Instant)——这将改善时间处理的安全性。