为什么我们要尝试Kotlin

技术雷达:对Android的完美支持为迅速发展的Kotlin语言提供了额外的推动力,我们也正在密切关注Kotlin / Native(基于LLVM,可以将Kotlin代码编译为原生可执行文件)的进展。在使用Anko库开发Android应用时,我们已经尝到了空指针安全、数据类和易于构建DSL的甜头。尽管初始编译速度慢,且只有IntelliJ才提供一流的IDE支持,但我们仍然建议尝试一下这种新颖简洁的现代语言。

由于最近在客户项目上有机会使用了Kotlin这门今年大热的语言,所以在好几个不同的场合都被要求做一些Kotlin相关的分享,在这个过程中被问到的最多的一个问题便是——我们为什么要尝试Kotlin?

(图片来自:http://suo.im/4hXHdp

实际上客户早在去年年初的时候便已经完成了他们的后端技术选型,而Kotlin在那个时候便已经成为了项目构建后端微服务的主要语言。所以在这些分享中我并没能给出一个很好的答案,而这个问题本身也引起了我的思考。

首先我们看看Kotlin语言的特点,官方罗列了4个显著的特点:

  • 简洁 Consice
  • 安全 Safe
  • 友好的开发工具 Tool-friendly
  • 和Java的互操作性 Interoperable

简洁 Concise

Kotlin的简洁体现在很多方面,对于Java程序员来说,最直接的体现便是在Kotlin语法中直接省略了分号,并且在构造一个类的实例时省略了new关键字,下面便是一段标准的Kotlin代码:

fun sayHi(name: String): String {
    val sb = StringBuilder(str = "Hi ")
    sb.append(name)
    return sb.toString()
}

让我们再看一个Kotlin官网的示例代码,来体会一下Kotlin的简洁:

data class Customer(val name: String, val email: String, val company: String)

简单的一行代码便实现了一个包含了constructor以及默认getters, toString, equals, hashCode和copy实现的Pojo,而同样的java实现需要大概50多行代码, 即便是在Lombok的帮助下仍然需要十几行代码。

Kotlin还在Java集合类的基础上进行了封装,并提供了非常丰富的集合操作。同时结合非常简洁的Lambda表达式,使得调用更加精简。

val numbers = 1..10
val doubles = numbers.map {it * 2}
val sumOfSquares = doubles.fold(0) {x,y -> x+y}

除了这些,Kotlin还提供了很多类似字符串模板、标准函数库、运算符重载的特性,这些特性使得代码可以非常简洁易读,极大提升了开发者的体验。

从实际项目来看,Kotlin的简洁在代码量上表现的非常明显,一个提供了24个API的Spring Boot微服务,通过Kotlin编写的代码量在8000行左右(含测试代码)。同样规模的微服务用Java编写的情况下代码量将远大于这个数字。

安全 Safe

许多编程语言(包括 Java)中最常见的陷阱之一是访问空的指针,导致空指针异常。Kotlin的安全性主要体现在它对Null-Safety的支持上。能够使代码在编译期间就察觉到可能的NullPointerException,让Java developer能够轻松摆脱NullPointerException。

var output: String
output = null // Compilation error

val name: String? = null // Nullable type
println(name.length()) // Compilation error

同时Kotlin还提供了一些自动转型及类型推断的特性,在提供安全检查的同时也带来了便捷。

下面也是一个来自官网的样例,Kotlin在类型检查得到true后,自动完成了Any到Invoice类型的转换:

fun calculateTotal(obj: Any) {
    if (obj is Invoice)
        obj.calculateTotal()
}

友好的开发工具 Tool-friendly

这个特点就不用多说了,引用官网那句傲娇的原话就是 “It’s what we do best!”

和Java的互操作性 Interoperable

简单来说这个特性就是Kotlin和Java是可以相互调用的。

这意味着我们可以利用任何已有的Java libraries来构建我们的应用,让我们无需放弃我们所熟悉的一切便可以享受Kotlin给我们带来的愉快的编程体验。

...
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.JpaSpecificationExecutor
...

interface AreaRepository : JpaRepository<AreaEntity, Long>, JpaSpecificationExecutor<AreaEntity> {
    fun existsByAreaId(id: UUID): Boolean
    fun findOneByAreaId(areaId: UUID): AreaEntity?
}

例子中是项目上一个用Kotlin编写的基于Spring JPA的Repository,可以看到得益于Interoperable的特性,在尝试使用Kotlin时我们可以依赖的是一个完整的Java生态圈。我们依然可以使用我们所熟悉的框架、构建工具、开发工具和测试工具。

如何开始?

看了这么吸引人的语言特性,或许你已经忍不住想要尝试Kotlin了。但是实际情况可能是项目已经开始了一段时间,我们已经用Java为项目构建了很多功能。这个时候引入一个新的语言可能会给项目带来一定的风险。那么我们可以如何开始呢?

使用Kotlin编写单元测试

如果你比较保守,那么你可以开始尝试在项目中仅通过Kotlin来编写单元测试,同样得益于Interoperable这个特性,我们可以轻松的使用Kotlin来为Java类编写单元测试。这样你可以不用担心尝试Kotlin为你的业务代码带来风险,同时也可以在编写单元测试的过程中尝试Kotlin语言的各种特性。

使用Kotlin来扩展

你还可以使用Kotlin来丰富项目中所用到的Library,使用Kotlin Extensions来在不需要继承的情况下完成对原有类型的扩展。或者直接通过Kotlin来编写工具类为项目服务。

//Extensions.kt
fun String.lastChar() = this.get(this.length - 1)
fun KPerson.fullName() = "${this.firstName} ${this.lastName}" //String template
//Java JUnit test
Test
public void lastChar() throws Exception {
 assertEquals('n', Extensions.lastChar("Kotlin"));
}

Test
public void fullName() throws Exception {
  KPerson k = new KPerson("Foo", "Bar", Gender.MALE, 18);
  assertEquals("Foo Bar", Extensions.fullName(k));
}

使用Kotlin来重写微服务

如果你在使用基于Spring Boot的微服务,那么完全可以挑选一个优先级较低的服务逐步通过Kotlin进行改写。你会发现这个转变会发生的无比顺滑。因为Kotlin不会改变你之前通过Spring Boot构建微服务的方式。

这三个方法都可以让你在风险可控的情况下尝试Kotlin。让你在感受Kotlin语言带来的美好编程体验的同时,使整个团队逐渐熟悉Kotlin语言。你将会发现对于一个Java程序员来说,学习Kotlin真的是一件非常容易的事情,可以说一旦开始你就再也回不去了。

技术雷达

正在我们还在犹豫是否要尝试Kotlin的时候,最新一期技术雷达上Kotlin的表现又给了我们一个难以抗拒Kotlin的理由。虽然在雷达的描述中,我们更关注的是Kotlin在Android Native领域的影响力,但是随着Spring社区对Kotlin的支持和更过成功项目的出现,相信Kotlin会继续向雷达的圆心迈进。

最后在这里分享一些我在学习Kotlin过程中觉得不错的相关资源:


更多精彩洞见,请关注微信公众号:思特沃克

Share

征服遗留系统

背景

就像晓强在第一个故事开篇所介绍的那样,如今,我们所交付的典型软件已经变成了由若干个Cloud Native Application所组成的分布式的微服务应用,但是在我们所服务的组织中,仍然存在着类似下面的这种巨大的老旧的单体应用,我们称之为遗留系统。

有些遗留系统仍然在组织中扮演着重要的角色,持续为客户提供着价值,而有些则已经成为了组织发展的瓶颈,无法适应业务的快速变化。这个故事便是关于我们是如何帮助客户有效的维护、提升、最终摆脱这些遗留系统的。

提到遗留系统,我们并不陌生也不缺乏案例。过往的大型项目为我们提供了很多值得分享的经典案例,能够从这些经验和总结中感受到在这些项目中所克服的挑战。这些挑战分别来自于:

  • 如何有效的积累遗留系统的上下文
  • 如何对遗留系统进行维护和变更
  • 如何能平滑的完成对遗留系统的技术迁移

积累上下文

万事开头难,当我们开始任何一项交付工作时,最关键的问题便是如何能够快速建立业务和技术上下文。而当我们开始接手一个遗留系统的时候,这个关键问题变得更加复杂和困难。

遗留系统的上下文就和一个没完成的拼图一样,碎片散落一地。有些信息只存在某些人深深的脑海里,有些信息在巨大而且过期的文档里,难辨真伪。代码不会撒谎,但是动辄几百万行的代码很容易让你迷失方向.

在应对这一难题上,我们从以往经验中形成了一套行之有效的方法。

首先,通过沟通和询问客户团队的业务或技术人员去了解到尽可能多的信息,然后通过阅读文档或在可用环境中演示来得到更具象的认识,最终通过阅读代码来解除剩下的疑问,完成巨大拼图中的一部分。

但是在实际操作中确仍然不简单。往往情况是这样的,在经过多次沟通和在文档中查阅信息后,获得的信息往往和代码中的无法对应起来,使整个过程需要不断的反复。我在去年经历的一次遗留系统改造项目中就有一次类似的经历,团队被这个令人沮丧的过程打败,做出了一些基于已有信息的假设,最终给项目的交付造成了不大不小的风险,团队付出了很大的代价才保证了项目的顺利交付。

在获得了上下文后,如何保持信息能得到及时的更新并有效的将信息共享给团队其他人是紧接着需要思考的问题。Specification by Example为我们提供了很好的方式将所有信息有效的管理起来,构建一套和代码一起管理的可执行的Live Document。但是对于一个遗留系统,这仍然是一个漫长和繁琐的过程。在这整个信息收集和记录的过程中,团队需要展现强大的耐心才能有效的达成目标为后面的工作打下基础。

开展变更

对于有些遗留系统我们只需要对其持续的监控,保证其能够正常的提供服务。但是在大多数情况下随着客户的业务不断变化,也会产生对遗留系统进行变更的需求,来迎合这些业务上的变化。那么如何在不破坏遗留系统的前提下修改遗留系统便成了应对遗留系统的第二个挑战。

用我们所推崇和坚持的一系列敏捷技术实践可以为遗留系统变更提供一张很好的保护网。

  • 在进行对遗留系统的修改工作之前,通过一定的单元测试覆盖,加上之前我们已经建立好的Live Document,能够为我们很好的提供质量保证。
  • 通过建立针对遗留系统的CI/CD Pipeline可以使我们在修改遗留系统时快速的得到反馈,对变更进行及时的验证。
  • 通过创建Stubs来Mock遗留系统的外部依赖则能帮助我们有效的缩短反馈环,可以大大增强我们对遗留系统进行变更的信心。

这些实践看起来和我们所交付的其他项目没有两样,但是当你需要为某个老旧语言编写的遗留系统提供单元测试覆盖的时候,当你的CI Pipeline需要支持一个老旧的商业中间件的自动化部署的时候,看似普通的技术实践则会变得困难重重。

这个时候将坚持这些实践作为原则变得尤为重要。这样才能为遗留系统的变更提供有效的保障。

技术迁移

当然仅凭耐心和原则是无法征服动辄几百万行代码的庞然大物的。应对遗留系统对技巧有着更高的要求。在这方面,我的ThoughtWorks同事们已经从过去的项目经历中总结和分享了很多应对遗留系统的技巧。特别是在对遗留系统进行技术迁移的过程中。比如:

  1. 影响结构图与特征草图的使用,帮助我们去梳理程序中各个模块之间的关系和依赖。
  2. Branch By Abstraction的使用,使我们可以逐渐的替换将系统中遗留的部分更新并剔除出去。
  3. Strangler Pattern的使用,让新老系统在一定的时间段内共存,使遗留系统能够平滑的迁移到新的技术架构。
  4. Feature Toggle的使用使我们能在部署后发现异常时快速的切换回老系统,使迁移风险降到了最低。
  5. 针对遗留系统的数据特点建立自定义的数据管道,完成遗留系统数据的迁移。

正是对这些技巧的灵活使用使我们真的做到了“旧的不变,新的创建,一步切换,旧的再见”。

写在最后

遗留系统是个难题,在应对一个巨大的遗留系统时没有捷径,同时也没有神奇的秘籍或令人目眩的黑科技。重要的是,团队需要意识到在面对一个遗留系统的时候我们需要具备:

更强大的耐心 – 去有效的收集和巩固遗留系统漫长发展过程中遗失的上下文。

更坚定的原则 – 去坚持敏捷技术实践,为遗留系统编织可靠的保护网给遗留系统的变更提供保障。

更丰富的技巧 – 去最大程度降低遗留系统技术迁移过程中对现有业务的影响,逐步平滑的完成遗留系统的迁移。


更多精彩内容,请关注微信公众号:软件乌托邦

Share