带你装逼带你飞 2019-08-28
当大规模采用一门新语言时,有许多不同的因素需要考虑,因为事情可能会发生巨大的变化。对于许多人来说,选择一门语言可以说是取决于个人偏好,但在 LinkedIn,我们有一个基础团队,负责评估这些基本技术决策的影响。最近,我们经历了评估 Android 开发语言的过程。从移动基础设施团队的角度来看,我想与你分享一下,我们在评估语言时所采取的步骤,以及为什么我们最终选择了 Kotlin 语言。
方法
大规模采用移动语言有许多不同的因素需要考虑。这样一个决定会对开发人员的生产力、招聘、培训成本以及最终产品产生巨大的影响。例如,你可能会提高开发人员的生产力,并能够交付更多的功能,但是必须付出应用程序大小增加的代价,这最终会影响用户的下载。为了更好地理解采用特定语言的投资和回报,我们认为,公正的技术分析是我们做出决定的基础。一旦做出了客观的评估,我们就可以制定一个最适合整个公司移动开发的路线图。
技术分析
在 Linkedin,Android 开发跨越了不同形式和规模的团队——从开发大型应用程序的大型团队,到开发多个中小型应用程序的小型团队,再到只专注于构建工具和基础设施的团队。为了尽可能全面地执行评估,我们成立了一个委员会,由每个应用程序团队的高级开发人员、基础设施团队和构建工具团队的代表组成。在 LinkedIn 的 Android 开发背景下,这些是我们研究的领域。
语言特性
当谷歌宣布将 Kotlin 作为 Android 开发的官方支持编程语言时,我们都非常激动。Kotlin 作为一门语言,具有许多 Java 世界中没有的新特性,比如空值安全(null-safety)、扩展、数据类、协程等等。虽然我们享受了这些新特性带来的所有好处,但是 Kotlin 还在设计时删除了某些 Java 特性,比如直接原始类型、静态成员、检查异常等等。在我们的分析中,我们深入了解了语言设计决策背后的想法,并研究了反编译代码来理解每个特性。这有助于我们将来制定指导方针,并对我们发布的代码充满信心。
兼容性
在 LinkedIn 的规模下,如果我们决定迁移到一种新的语言,迁移将是一个很长的过程,尤其是对于包含数百万行代码的代码库。正如理解每种语言的特性很重要一样,考虑互操作性也同样重要。幸运的是,Kotlin 和 Android Studio 之间的集成经常提供建议并帮助你重构代码。然而,归根结底,它们是两种不同的语言,让它正常工作只是一个开始。在此过程中,你肯定希望构建一些增强功能。例如,我们遇到的一些问题是为静态成员添加 JVM 注解,并确保 Java API 把单一抽象方法(SAM)转换 参数放在最后,这样 Kotlin 编译器就可以使用拖尾 lambda 语法 。
扩展性
构建时间是移动工程师最优先考虑的问题之一。使用 Kotlin,我们预计构建时间会增加。LinkedIn 的移动开发从数千行代码到数百万行代码不等;因此,构建时间的增加可能有很大差异。这可能不会立即引起注意,但随着时间的推移,我们会密切关注转向 Kotlin 所导致的时间增加,尤其是 LinkedIn 的规模下。为了测试构建时间的长短,我们使用Android Studio Poet 创建了包含不同模块数量、不同 Kotlin 和 Java 代码行的 Android 项目作为模拟,同时保持类似的项目大小和依赖关系图。我们使用Gradle Profiler 测量了清除构建和增量构建,以了解构建时间花在哪里。
考虑到该项目的规模,它有可能影响从开发到生产的许多不同方面。运行时性能是该分析的另一个关键领域。我们在 Kotlin 和 Java 中实现了几种算法作为测试工具,并在物理设备上运行它们。为了使实验更加真实,我们还在这些测试中启用了ProGuard 。研究结果与“Kotlin 和 Java 在 Android 运行时上的性能评估 ”结果一致。有趣的是,Kotlin 中的大部分开销来自空安值全特性,该特性使方法的每个调用都具有非可选参数,都不能被编译器映射到基本类型。这就导致了对 Intrinsics.checkParameterIsNotNull 的调用,它要为我们实验中的大部分开销负责。
应用程序的大小是由许多因素造成的。按照谷歌的说法 ,初始的 Kotlin 标准库导入将使应用程序的大小增加大约 1MB,随着时间的推移,更多的 Java 代码被转换成 Kotlin,由于 Kotlin 字节码方法的增加,应用程序将变得更大。幸运的是,ProGuard 能够从 Kotlin 生成的代码中去除并大大减少未使用的方法。我们创建了示例应用程序,并将它们从 Java 转换为纯 Kotlin,以了解方法数量和应用程序大小的开销。
语言升级
移动开发的前景在不断变化,了解升级过程非常重要。在过去,我们看到语言升级伴随着不兼容的更改、构建失败和许多意外错误,这些错误最终迫使我们锁定主干,甚至执行语言升级。JetBrains 承诺为 Kotlin 提供两种形式的向后兼容性:
新编译器仍将可以使用旧二进制文件(但是旧编译器可能无法识别新二进制文件,比如 javac 1.6,它无法读取由 javac 1.8 编译的类)。
旧二进制文件将可以在运行时继续和新二进制文件一起工作,而新代码可能需要新依赖项。
在源代码兼容性方面,Kotlin 遵循 API 弃用警告的模式,一旦升级到新的编译器版本,该警告就会逐渐变为错误。在过去,我们看到升级通常会给社区足够的时间来适应这些更改。例如,KT-21515 中的语言行为变化被标记为警告,并在接下来的 1.2.x 补丁版本中得以保留,直到 Kotlin 1.3。然而,值得注意的是,JetBrains 并没有声明将在主要版本更改中提供哪种类型的兼容性。
开发者生态系统
Kotlin 能够轻而易举地与 Android Studio 搭配使用并不奇怪,因为 JetBrains 是这两款产品背后的主要贡献者。大多数(如果不是全部的话)Android Studio 工具都可以无缝地与 Kotlin 一起工作,包括调试器、内存分析器、Android 分析器等等。值得一提的 Kotlin 特性包括 Kotlin 自动转换和 Kotlin 反编译器。后者让你深入了解 Kotlin 代码的底层工作机制。
LinkedIn 使用 Gradle 作为 Android 和 Java 产品的实际构建系统。借助 Kotlin- Android Gradle 插件,Kotlin 在 Gradle 中可以顺畅地工作。Gradle 企业版提供远程缓存解决方案。Kotlin 1.2.21 允许 Kotlin 项目使用构建缓存。具体来说,对于 Gradle 构建缓存,它还需要使用 Kotlin Gradle Plugin 1.2.20 和 Gradle 4.3 或更高版本。总的来说,由于采用 Kotlin 而引起的构建工具的变化很少。然而,我们的分析主要集中在 Gradle 上,可能不适用于其他构建系统,比如 Buck。
正如我们关注开发人员的生产力一样,质量也是我们要考虑的另一个重要因素。编写测试对于确保代码质量至关重要。我们花了一些时间来理解测试生态系统的局限性及其与其他语言的兼容性。这不仅包括测试框架,还包括代码覆盖率分析、CI 管道,最重要的是,在开始编写 Kotlin 测试之前,在不违背测试标准的情况下所要采取的步骤。该分析还包括 Android 中的主要静态分析工具Android Lint ,它现在使用UAST 支持 Java 和 Kotlin 分析及样式检查工具。
行业分析
自从 Kotlin 于 2017 年在谷歌 I/O 大会上宣布成为 Android 开发的一等语言以来,越来越多的工程师开始接受 Kotlin。Stack Overflow 2018 调查显示,Kotlin 是第二受欢迎的编程语言。Kotlin 还被认为是未来语言中增长最快的语言,与 Swift、Go、Haskell 和 Rust 并列第一。据谷歌报道,2017 年,当 Kotlin 被宣布为 Android 开发的第一语言时,Google Play 中 9% 的应用程序已经在使用 Kotlin。一年后,即 2018 年 5 月,谷歌宣布这一数字已增至 35%,同比增长近 6 倍。
我们分析了Google Play 商店中的前 300 个免费应用程序 ,并根据它们的 DEX 文件大小进行了筛选,以了解有多少应用程序在大规模使用 Kotlin。虽然这个数字包含了所有第三方库的代码,但它能很好地反映应用程序代码的大小。作为一个初步步骤,我们排除了 APK 中没有专门 Kotlin 文件夹的应用程序,该文件夹将包含 Kotlin 运行时使用的一些 Kotlin 内置文件。由于代码的混淆,计算 Kotlin 在代码中的百分比非常棘手。但是,我们使用apktool 将所有 DEX 文件转换为 SMALI 代码,并发现了两个不同的线索,它们标识了用 Kotlin 编写的类。对于没有从.source 属性中删除原始文件名的应用程序,我们可以提取扩展名 *.kt。对于其他应用程序,我们寻找 kotlin.Metadata 类。总的来说,我们能够建立一个图表来显示应用程序及其类的数量、代码大小和 Kotlin 百分比。这让我们了解到社区有多成熟,以及我们可以从合作伙伴那里得到多少支持。
起初,大多数库所有者认为,在自己的代码中使用 Kotlin 实现细节是不成熟的,不应该强加给用户。随着 Kotlin 社区变得越来越活跃,这种观点已经开始转变。Square 是许多最受欢迎的 Android 开源库的所有者,它宣布他们的核心Okio 库已经用Kotlin 重写 ,并且他们将在未来的所有版本中使用Kotlin。其他一些库项目已经选择遵循谷歌针对Android KTX 提供的示例,并发布了一个用 Java 编写的基本库,在其中单独的构件中添加了特定于 Kotlin 的扩展点。例如,FasterXML 的 Jackson 项目添加了一个用于序列化和反序列化 Kotlin 类的Kotlin 模块 ,以及 Mockito,该项目正在研究添加一个针对 Kotlin 优化过的 mock 语法 。
全方位考察