改用任何新语言都是一项艰巨的任务。成功的秘诀在于缓慢起步、循序渐进和经常测试,以使您的团队走向成功。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 + '\'' +
'}';
}}
您可以将 Java 类替换为一行 Kotlin 代码,如下所示:
data class Person(var firstName: String?, var lastName : String?)
随后可以针对当前的测试套件对此代码进行单元测试。我们的想法是从小规模入手,一次迁移一个模型,并转换大部分是状态而非行为的类。请务必在此过程中进行测试。
迁移测试
需要考虑的另一个起步途径是转换现有的测试并开始用 Kotlin 编写新的测试。这样能让您的团队有时间先熟悉这种语言,然后再编写计划随 APK 一起提供的代码。
将实用程序方法移至扩展函数
任何静态实用程序类(
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 调用这些函数。
...val myString: String = ...val fooString = myString.foo()...
完成迁移
您的团队熟悉 Kotlin 并且您迁移了较小的部分后,您可以继续处理较大的组件,如 Fragment、Activity、
ViewModel
对象,以及与业务逻辑相关的其他类。
注意事项
就像 Java 有特定的样式一样,Kotlin 也有自己的惯用样式,正是这种样式促成了它的简洁。不过,一开始您可能会发现,您的团队生成的 Kotlin 代码看起来更像是要替换的 Java 代码。随着您的团队不断积累 Kotlin 使用经验,这种情况会逐步改变。切记,逐步改变是取得成功的关键。
随着 Kotlin 代码库不断增长,您可以采取以下几项措施来保持一致性:
通用编码标准
请务必在采用过程中尽早制定一套标准的编码规范。只要合理,您的规范可以背离 Android Kotlin 样式指南。
静态分析工具
使用 Android Lint 和其他静态分析工具强制执行为您的团队制定的编码标准。第三方 Kotlin Linter klint 也针对 Kotlin 提供了额外的规则。
持续集成
请确保符合通用编码标准,并为 Kotlin 代码提供足够的测试覆盖范围。将此纳入自动构建流程有助于确保一致性和遵循这些标准。
互操作性
Kotlin 在大多数情况下可与 Java 无缝互操作,但请注意以下几点。
可为 null 性
Kotlin 依靠编译的代码中的可为 null 性注释在 Kotlin 端推断可为 null 性。如果未提供注释,则 Kotlin 默认采用平台类型(可将其视为可为 null 类型,也可将其视为不可为 null 类型)。不过,如果不小心处理,这可能会导致运行时
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? = nullif (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}
注意
:智能类型转换仅适用于只读类属性(比如声明为
val
的类属性)和局部函数变量,因为 Kotlin 编译器不能保证在
if
保护语句与变量的使用之间不会将变量引用重新设为
null
。
测试
在 Kotlin 中,默认情况下,类及其函数处于关闭状态,不能扩展。您必须明确打开要子类化的类和函数。此行为是一种语言设计决策,旨在促进代码的编写而不是继承。Kotlin 具有内置支持,可通过委托实现行为,以帮助简化代码的编写。