Kotlin logo

Kotlin

A concise multiplatform language developed by JetBrains

在组织内扩大 Kotlin 采用

Read this post in other languages:

客座文章作者:Urs Peter,高级软件工程师兼 JetBrains 认证 Kotlin 培训师。对于希望以更系统的方式提升 Kotlin 技能的读者,Urs 还在 Xebia Academy 主办了 Kotlin 技能提升计划

本文为在以 Java 为主的环境中成功采用 Kotlin 的终极指南系列的第五篇文章,系列将从一位开发者产生好奇心到公司范围转型这一发展过程,逐步讲述 Kotlin 采用在实际团队中如何增长。

本系列的所有内容:

  1. 面向 Java 开发者的 Kotlin 使用入门
  2. 在实际项目中评估 Kotlin
  3. 在贵公司中扩大 Kotlin 的采用
  4. 帮助决策者认可 Kotlin
  5. 在组织内扩大 Kotlin 采用

大规模采用 Kotlin 的成功因素

让开发者接受 Kotlin 并获得管理层支持是一个重要的里程碑,但这并非终点。当您需要让现有 Java 代码库与 Kotlin 共存或向其迁移时,真正的挑战才刚刚开始。如何才能高效应对这种混合式环境?

管理遗留代码库的关键在于制定一套与组织目标和业务实际相契合的策略。以下是一套经实践验证行之有效的方案。

应用程序生命周期策略

不同应用程序需根据其所处的生命周期阶段采用不同的方案。我们将探讨三个不同类别:

服务终止的应用程序

策略:维持现状。

如果某个应用程序已列入退役计划,则迁移不具备商业合理性。保留这些系统的 Java 技术栈,将精力集中在重要事项上。结合应用程序的剩余使用周期来看,迁移成本完全不具备合理性。

新系统

策略:默认采用 Kotlin。

在 Kotlin 采用已完成的组织中,全新项目自然会以 Kotlin 为起始技术栈。如果仍处于采用阶段,团队通常可以在 Java 与 Kotlin 之间选择。需要明智决策哦 ;-)。

在用应用程序

策略:以功能为基础的务实迁移方案。

在用应用程序需要审慎考量:如果单纯为了重写而重写,将很难获得产品负责人的认可。相反,应将迁移工作与新功能开发相结合。这种方式既能创造切实的业务价值,又能实现代码库的现代化升级。我们已在扩展/转换现有 Java 应用程序一节中探讨不同的切入角度。

Java 转 Kotlin 转换方案

将 Java 转换为 Kotlin 时,您有多种选择,每种方案都有其明显的利弊权衡:

1. 完全重写

适用场景:小型代码库 

挑战:大型系统耗时费力

从头重写代码库可以获得最简洁、最贴合 Kotlin 惯用写法的代码。此方案适用于小型代码库,如微服务。而对于大型代码库,此方案的成本通常高到难以承受。

2. IDE 自动转换 + 人工优化

适用场景:有专门重构时间的中型代码库

挑战:必须进行人工优化 

IntelliJ IDEA 的 Java 转 Kotlin 功能仅可以实现字面意义上的转换,远未达到 Kotlin 的惯用写法标准。以下为示例:

方案 1: 

Java

record Developer(
       String name,
       List languages
) {}

原始自动转换结果:

Kotlin

@JvmRecord
data class Developer(
   val name: String?,
   val languages: MutableList?
)

此转换存在以下几个问题:

  • 所有内容均变为可为 null 类型(过度防御)。
  • Java 集合会转为 Kotlin 的 MutableList,而非 Kotlin 的默认只读 List。

借助 jspecify 注解改进转换效果:

幸运的是,对于将所有 Java 类型转换为 Kotlin 中的可为 null 类型,可以通过 @NonNull/@Nullable 注解解决。目前有多种可选方案,其中最现代化的是 jspecify,该注解库近期已获得 Spring 官方支持:

   org.jspecify
   jspecify
   1.0.0


implementation("org.jspecify:jspecify:1.0.0")

借助 jspecify,我们可以在 Java 代码中添加 @Nullable 和 @NonNull 注解。

方案 2: 

Java

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

record Developer(
       @NonNull String name, 
       @NonNull List languages,
       @Nullable String email
) {}

此时,自动转换的效果会好很多:

Kotlin

@JvmRecord
data class Developer(
   //😃 non-null as requested
   val name: String, 
   //😃 both, collection and type are non-null
  val languages: MutableList,
   //😃 nullable as requested
  val email: String?, 
)

自动转换方案的局限性:

即便借助 jspecify 注解,复杂的 Java 模式也无法很好地转换。有关自动转换代码示例,请参考 3. Kotlin 摒弃了 checked 异常,代码安全性却更胜一筹一节:

Kotlin

fun downloadAndGetLargestFile(urls: MutableList): String? {
     //🤨 using Stream, instead of Kotlin Collections
     val contents = urls.stream().map { urlStr: String? ->
     //🤨 still using Optional, rather than Nullable types
     //🤨 var but we want val!
     var optional: Optional
     //🤨 useless try-catch, no need to catch in Kotlin
     try {
         optional = Optional.of(URI(urlStr).toURL())
     } catch (e: URISyntaxException) {
           optional = Optional.empty()
     } catch (e: MalformedURLException) {
           optional = Optional.empty()
     }
     optional
   }.filter { it!!.isPresent() }//🤨 discouraged !! to force conversion to non-null
    .map {it.get() }
    .map { url: URL ->
      //🤨 useless try-catch, no need to catch in Kotlin
      try {
        url.openStream().use { `is` -> 
           String(`is`.readAllBytes(), StandardCharsets.UTF_8)
        }
      } catch (e: IOException) {
         throw IllegalArgumentException(e)
       }
    }.toList()
   //🤨 usage of Java collections…
   return Collections.max(contents)
}

自动转换的结果与理想中贴合惯用写法的效果相去甚远:

Kotlin

fun downloadAndGetLargestFile(urls: List): String? =
   urls.mapNotNull {
       runCatching { URI(it).toURL() }.getOrNull()
   }.maxOfOrNull{ it.openStream().use{ it.reader().readText() } }

自动转换仅能提供一个起点,而要得到真正贴合 Kotlin 语言特性的代码,还需进行大量人工优化,且要求开发者掌握 Kotlin 惯用写法相关知识。

3. AI 辅助转换

适用场景:具备完善测试基础架构的大型代码库

挑战:需进行人工审核

相比基础自动转换,AI 有望生成更贴合 Kotlin 惯用写法的结果,但要确保转换成功,需做好周密的准备:

前提:

  1. 全面的测试覆盖:由于 LLM 具有不可预测性,您需要可靠的测试来捕获 AI 幻觉。
  2. 精心设计的系统提示词:制定详细的指令,明确贴合团队标准的 Kotlin 惯用写法转换要求。您可以将此系统提示词作为起点。
  3. 全面深入的代码审查 :AI 输出需要经过全面审查,以确保逻辑准确性和惯用写法的正确性,对于大型代码库而言,这一过程可能会带来较大的脑力负担。

借助建议的系统提示词指导转换工作,最终结果相当令人满意,但仍非完美:

Kotlin

fun downloadAndGetLargestFile(urls: List): String? {
   val contents = urls
      .mapNotNull { 
            urlStr -> runCatching { URI(urlStr).toURL() }.getOrNull() 
      }.mapNotNull { url -> runCatching { 
            url.openStream().use { it.readAllBytes().toString(UTF_8)
         } 
      }.getOrNull() }

   return contents.maxOrNull()
}

4. 大规模自动转换

适用场景:需要进行系统性转换的超大型代码库

目前,尚无官方工具支持将 Java 代码库大规模转换为贴合 Kotlin 惯用写法的形式。不过,Meta 和 Uber 均已成功攻克这一难题,他们为 Android 代码库打造的解决方案同样适用于后端应用程序。以下文档与演讲将为您揭示 Meta 和 Uber 是如何推进这一探索的:

Meta 的解决方案:

Meta

Uber 的解决方案:

Uber

  • 策略:使用 AI 生成转换规则,进行基于规则的确定性转换
  • 资源KotlinConf 演示

两家公司的成功源于构建了系统化、可重复的流程,而非依赖人工转换或简单的自动化。他们基于规则的方案确保了数百万行代码的一致性与质量。

重要提示:大规模将 Java 转为 Kotlin 会面临团队协作层面的挑战:为保障可靠性,仍需“人工介入”审查生成的代码。但如果规划不当,自动化转换产生的大量拉取请求很容易让工程师不堪重负。因此,必须审慎考量对团队协作的影响。 

请记住,成功的大规模 Kotlin 采用绝非单纯的代码转换,更核心的是培养团队技术能力、建立编码标准,并搭建可持续的流程,为组织创造长期价值。

方案速览:

  • 小型应用程序,或本就计划重写?
    使用 Kotlin 重写 (1)
  • 中型代码库,且有专门重构时间或团队正在学习 Kotlin?
    IntelliJ IDEA 自动转换 + 优化(先进行测试)(2)
  • 中大型代码库,存在重复模式且测试完善?
    AI 辅助方案 (3)
  • 组织级跨多服务 Kotlin 迁移?
    按照具体方案大规模自动转换(平台主导)(4)

释放 Kotlin 的全部潜能

Kotlin 可以快速提升开发者效率。它简洁的语法、安全特性与丰富的标准库可以帮助众多开发者在数周内编写出更优质的代码 – 通常通过自学或在职学习即可实现。但如果缺乏指导,许多人容易陷入以 Java 式方式使用 Kotlin 的误区:执着于可变结构、沿用繁琐的模式,却忽略了特有的惯用特性。

迈向进阶之路

迈向进阶的关键 – 拥抱不可变性、表达式导向代码、DSL,以及基于协程的结构化并发(支持或不支持虚拟线程),正是许多开发者的卡点所在。

在这个阶段,我发现外部培训比自学能带来显著更优的效果。即便是拥有多年 Kotlin 经验的开发者,也常常能从针对性辅导中受益,从而更好地运用惯用模式,释放这门语言的全部潜能。

Kotlin Heroes

是否采用 Kotlin:贵公司想成为哪种类型的公司?

采用 Kotlin 的决策本质上是对团队工程文化的映射。您更看重以下哪种价值取向?

  • 传统方案 (Java):稳健保守、流程规范、稳定性强。
  • 进阶方案 (Kotlin):务实高效、现代前沿、灵活适配。

两种方案各有其优势。Java 能够稳妥完成任务。而 Kotlin 往往能以更优方式达成目标,不仅能提升开发者幸福感,还能减少 bug。问题不在于 Kotlin 是否更出色,而在于您的组织是否愿意投入资源,追求更高的水准。

从首次 Kotlin 测试到全组织规模化采用,这条迁移之路未必一帆风顺,但只要策略得当,成功是完全可预期的。不妨从小规模试点起步,验证价值,建立社区,再审慎地扩大规模。

组织现有和未来的开发者终将为此而感激您。

Urs Peter

Urs 是一位经验丰富的软件工程师、解决方案架构师、会议演讲嘉宾和培训师,在构建弹性、可扩缩和任务关键型系统方面拥有 20 余年的丰富经验,主要涉及 Kotlin 和 Scala。

除了担任顾问之外,他还是一位充满热情的培训师,并撰写了丰富多样的课程,涉及从 Kotlin 和 Scala 语言课程到微服务和事件驱动型架构等架构培训的众多内容。

作为天生社交达人,他喜欢在聚会和会议上分享知识,启发同行并获得同行启发。Urs 是一位 JetBrains 认证 Kotlin 培训师。

image description

Discover more