迁移到任何新的语言都可能是一项艰巨的任务。成功的秘诀是循序渐进,分块迁移,并频繁测试以确保团队的成功。Kotlin 使迁移变得容易,因为它编译成 JVM 字节码,并且与 Java 完全互操作。
团队建设
迁移之前的第一步是为您的团队建立共同的基础理解。以下是一些您可能会发现有助于加快团队学习速度的技巧。
组建学习小组
学习小组是促进学习和知识保留的有效方法。研究表明,在小组环境中背诵所学内容有助于强化学习材料。为小组的每个成员准备一本Kotlin书籍或其他学习资料,并要求小组每周学习几章内容。在每次会议期间,小组应比较他们所学的内容,并讨论任何问题或观察结果。
营造教学文化
虽然并非每个人都认为自己是老师,但每个人都可以教别人。从技术或团队负责人到个人贡献者,每个人都可以鼓励学习环境,以确保成功。促进这一点的一种方法是定期举行演示,让团队中的一名成员讲解他们所学到的内容或想分享的内容。您可以利用您的学习小组,要求志愿者每周演示一个新的章节,直到您的团队对该语言感到舒适为止。
指定一位负责人
最后,指定一位负责人来领导学习工作。在您开始采用过程时,此人可以充当 *主题专家* (SME)。重要的是要让此人参与您所有与 Kotlin 相关的实践会议。理想情况下,这个人已经对 Kotlin 感兴趣,并且具有一定的实践知识。
缓慢集成
缓慢开始并战略性地思考首先迁移哪些生态系统部分至关重要。通常最好将其隔离到组织内的单个应用,而不是旗舰应用。就迁移所选应用而言,每个情况都不同,但以下是一些常见的起点。
数据模型
您的数据模型可能包含大量状态信息以及一些方法。数据模型也可能包含一些常见的方法,例如 toString()
、equals()
和 hashcode()
。这些方法通常可以轻松地单独转换和单元测试。
例如,假设以下 Java 代码段
public class Person {
private String firstName;
private String lastName;
// ...
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(firstName, person.firstName) &&
Objects.equals(lastName, person.lastName);
}
@Override
public int hashCode() {
return Objects.hash(firstName, lastName);
}
@Override
public String toString() {
return "Person{" +
"firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
'}';
}
}
您可以使用一行 Kotlin 代码替换 Java 类,如下所示
data class Person(var firstName: String?, var lastName : String?)
此代码随后可以针对您当前的测试套件进行单元测试。这里的思路是从一个模型开始,逐步过渡到主要包含状态而不是行为的类。请务必经常进行测试。
迁移测试
另一个可考虑的起始路径是转换现有测试,并开始使用 Kotlin 编写新测试。这可以让您的团队在编写计划随应用一起发布的代码之前,有时间熟悉 Kotlin 语言。
将实用程序方法移动到扩展函数
任何静态实用程序类(StringUtils
、IntegerUtils
、DateUtils
、YourCustomTypeUtils
等)都可以表示为Kotlin 扩展函数,并由您现有的 Java 代码库使用。
例如,假设您有一个包含一些方法的StringUtils
类
package com.java.project;
public class StringUtils {
public static String foo(String receiver) {
return receiver...; // Transform the receiver in some way
}
public static String bar(String receiver) {
return receiver...; // Transform the receiver in some way
}
}
然后,这些方法可能会在您的应用中的其他地方使用,如下例所示
...
String myString = ...
String fooString = StringUtils.foo(myString);
...
使用 Kotlin 扩展函数,您可以为 Java 调用者提供相同的Utils
接口,同时为不断增长的 Kotlin 代码库提供更简洁的 API。
为此,您可以首先使用 IDE 提供的自动转换将此Utils
类转换为 Kotlin。示例输出可能类似于以下内容
package com.java.project
object StringUtils {
fun foo(receiver: String): String {
return receiver...; // Transform the receiver in some way
}
fun bar(receiver: String): String {
return receiver...; // Transform the receiver in some way
}
}
接下来,删除类或对象的定义,在每个函数名前添加此函数应适用的类型,并使用此来引用函数内的类型,如下例所示
package com.java.project
fun String.foo(): String {
return this...; // Transform the receiver in some way
}
fun String.bar(): String {
return this...; // Transform the receiver in some way
}
最后,在源文件顶部添加JvmName
注释,以使编译后的名称与应用的其余部分兼容,如下例所示
@file:JvmName("StringUtils")
package com.java.project
...
最终版本应类似于以下内容
@file:JvmName("StringUtils")
package com.java.project
fun String.foo(): String {
return this...; // Transform `this` string in some way
}
fun String.bar(): String {
return this...; // Transform `this` string in some way
}
请注意,现在可以使用 Java 或 Kotlin 调用这些函数,并且约定与每种语言相匹配。
Kotlin
... val myString: String = ... val fooString = myString.foo() ...
Java
... String myString = ... String fooString = StringUtils.foo(myString); ...
完成迁移
一旦您的团队熟悉了 Kotlin,并且您已经迁移了较小的区域,就可以继续处理更大的组件,例如片段、活动、ViewModel
对象以及其他与业务逻辑相关的类。
注意事项
就像 Java 有特定的风格一样,Kotlin 也有其自身的惯用风格,这有助于提高其简洁性。但是,您最初可能会发现团队生成的 Kotlin 代码更像是它正在替换的 Java 代码。随着团队的 Kotlin 经验增长,这种情况会随着时间的推移而改变。请记住,循序渐进的改变是成功的关键。
以下是一些在 Kotlin 代码库不断发展过程中可以做到保持一致性的事情
常见的编码标准
请务必在采用流程的早期定义一组标准的编码约定。在有意义的情况下,您可以偏离 Android Kotlin 风格指南。
静态分析工具
通过使用Android lint和其他静态分析工具来强制执行为您的团队设置的编码标准。klint(一个第三方 Kotlin 代码风格检查工具)也为 Kotlin 提供了额外的规则。
持续集成
请务必符合常见的编码标准,并为您的 Kotlin 代码提供足够的测试覆盖率。将其作为自动化构建过程的一部分,有助于确保一致性和对这些标准的遵守。
互操作性
Kotlin 在大多数情况下可以与 Java 无缝互操作,但请注意以下几点。
可空性
Kotlin 依赖于编译代码中的可空性注释来推断 Kotlin 侧的可空性。如果未提供注释,Kotlin 默认使用平台类型,该类型可以被视为可空类型或不可空类型。但是,如果处理不当,这可能会导致运行时NullPointerException
问题。
采用新功能
Kotlin 提供了许多新的库和语法糖来减少样板代码,这有助于提高开发速度。也就是说,在使用 Kotlin 的标准库函数(例如集合函数、协程和lambda 表达式)时,要谨慎和有条理。
以下是较新的 Kotlin 开发人员经常遇到的一个非常常见的陷阱。假设有以下 Kotlin 代码
val nullableFoo: Foo? = ...
// This lambda executes only if nullableFoo is not null
// and `foo` is of the non-nullable Foo type
nullableFoo?.let { foo ->
foo.baz()
foo.zap()
}
此示例的目的是如果nullableFoo
不为 null,则执行foo.baz()
和foo.zap()
,从而避免NullPointerException
。虽然此代码按预期工作,但它不如简单的 null 检查和智能转换更易于阅读,如下例所示
val nullableFoo: Foo? = null
if (nullableFoo != null) {
nullableFoo.baz() // Using !! or ?. isn't required; the Kotlin compiler infers non-nullability
nullableFoo.zap() // from guard condition; smart casts nullableFoo to Foo inside this block
}
测试
默认情况下,Kotlin 中的类及其函数是封闭的,不允许扩展。您必须显式打开要子类的类和函数。此行为是语言设计决策,旨在促进组合而不是继承。Kotlin 内置支持通过委托来实现行为,以帮助简化组合。
此行为对于 Mockito 等模拟框架来说是一个问题,这些框架依赖于接口实现或继承在测试期间覆盖行为。对于单元测试,您可以启用 Mockito 的Mock Maker Inline功能,该功能允许您模拟最终类和方法。或者,您可以使用All-Open 编译器插件打开要测试的任何 Kotlin 类及其成员,作为编译过程的一部分。使用此插件的主要优点是它适用于单元测试和工具测试。
更多信息
有关使用 Kotlin 的更多信息,请查看以下链接
- Android 的 Kotlin 优先方法
- Kotlin 入门资源
- Java 用户学习 Kotlin 的资源
- Java 到 Kotlin 学习路径,这是一组帮助 Java 程序员学习和编写惯用 Kotlin 代码的资源。