编程语言从架构师视角看是否该用Kotlin做服务端开发

    作者:chentao106更新于: 2020-02-16 14:50:54

    大神带你学编程,欢迎选课

    从架构师视角看是否该用Kotlin做服务端开发?高级语言的出现使得计算机程序设计语言不再过度地依赖某种特定的机器或环境。这是因为高级语言在不同的平台上会被编译成不同的机器语言,而不是直接被机器执行。最早出现的编程语言之一FORTRAN的一个主要目标,就是实现平台独立。

    本文从研发团队的视角,来审视在服务端开发项目中是否应该使用Kotlin。而关于Kotlin语言细节,详情请大家参移动Kotlin官方文档或搜索引擎去了解更多,本文就不详细展开。

    编程语言从架构师视角看是否该用Kotlin做服务端开发_Java编程语言_Java视频_Javascript视频_课课家

    前言

    自从Oracle收购Sun之后,对java收费或加强控制的尝试从未间断,谷歌与Oracle围绕Java API的官司也跌宕起伏。虽然Oracle只是针对Oracle JDK8的升级收费,并释放了OpenJDK一直开源这份善意,但是如果没有各个大非Oracle的JVM、JDK和众多其它基于JVM的语言,Oracle这份善意能维持到什么时候可不好说。

    大厂要从JVM和JDK的层面早做打算,而广大中小企业,就只能先从Java语言的层面,先找到Oracle以外的备胎。自从被谷歌钦定为Android开发首选语言之后,采用Apache2.0 License的Kotlin逐渐进入大众的视野。从领域语言到通用语言,基于JVM的语言选择众多,Kotlin能脱颖而出被谷歌相中,除了License的友好外,自然有其独到之处(个人觉得基于Python语法的Jython在当时也算是一个强强联合的选择,当然现在来看Kotlin的优势明显)。我们先从摘自网上的一个段子来感受下Kotlin的特点:

    •  Scala:想解决Java表达能力不足的问题
    •  Groovy:想解决Java语法过于冗长的问题
    •  Clojure:想解决Java没有函数式编程的问题
    •  Kotlin:想解决Java

    当然,团队开发比个人开发要考虑的问题更多,本文从研发团队的视角,来审视在服务端开发项目中是否应该使用Kotlin。而关于Kotlin语言细节,详情请大家参移动Kotlin官方文档或搜索引擎去了解更多,本文就不详细展开。

    选择Kotlin的理由

    1. 与Java近乎完美的兼容

    作为团队技术预研,我已小试牛刀使用Kotlin做了一小一中真实上线项目,包括CI\\CD、Spring全家桶、MyBatis、RDMS、NoSQL、消息队列、微服务、RESTful接口、鉴权、计费等全面实践,除项目代码之外的也用Kotlin实现了爬虫、运维、测试工具等。这其中除了BenchMark性能测试因OpenJDK:jmh直接操作字节码而需要编译比java略复杂外,所有事件都能做到人(Java)无我(Kotlin)有,人(Java)有我(Kotlin)强,只身十天左右就能高质量从零完成一套AI SaaS服务的开发、测试。

    我自身的实践可以说明,Kotlin使用Java的开源组件、类库、工具栈上与Java代码本身几乎(仅发现jmh有些许差别外)没有差别,这是其它基于JVM的语言无法比拟有优势。

    2. 更易写出可靠的代码

    2.1 强制非空校验

    1. // 所有变量默认不能为空  
    2. var a: String = "abc"  
    3. a = null //编译错误  
    4. // 如果想要允许变量为空,变量声明中的类型以"?"结尾  
    5. var b: String? = null  
    6. // 非空类型的变量,可以直接调用对象方法  
    7. println(a.length)  
    8.  // 可空类型的变量,可以用"?."替换"."来进行完全调用  
    9. println(b?.length) //如果b为null,则b?.length返回null,而不会抛出NPE  
    10. // "?."链式调用更显安全调用的威力  
    11. bob?.department?.head?.name //只要有一环为null,则整个表达式返回null  
    12. // 空类型不能直接赋值给非空类型  
    13. a = b //编译错误  
    14. // 可以使用非空断言"!!"来强制将b转换为其对应的非空类型  
    15. a = b!! //如果b为null,则抛出NPE,否则赋值成功  
    16. // 更安全的做法是使用"?:"赋值  
    17. a = b?:"Default"  
    18. // "?:"还可以接”throw“、"return"、"break"语句来提前结束函数或语句块 
    19. a = b?: throw MyException("自定义异常") 

    可以看到,Kotlin通过强制非空校验机制,规避了Java最易犯的NPE问题,并且在不额外增加判空代码的情况下,很容易写出空安全的代码,当然也通过非空断言保留了抛出NPE的途径,具体请参考Kotlin官方文档-空安全

    2.2 只读变量

    1. // 通过val可以方便声明只读变量,防止变量的误修改,类似于java的final  
    2. val a = "Hello"  
    3. //...  
    4. a = "world" //编译错误  
    5. // var用于声明可读写变量  
    6. var b = 1 //如果下方没有代码对b进行修改,则编译告警 

    声明只读变量的语法更简洁,配合var未修改编译告警,强制开发人员明白无误地声明变量

    3. 简洁高效,代码量可减少逾50%

    3.1 默认参数与数据类

    1. // 默认参数,大多数情况可以代替函数重载  
    2. fun a(name: String, age: Int = 0) {  
    3.   //...  
    4. }  
    5. // data关键字会自动为类型增加hashCode(),equals(),copy(),toString()方法  
    6. // val关键字会自动生成只读成员变量及get方法  
    7. // var关键字会自动生成成员变量及get和set方法  
    8. data class Person(val id: Int, var name: String, var age: Int = 0)  
    9. //以上就是Person实体的完整声明,它等价于十行以上的java代码 

    默认参数、数据类、主构造函数的组合使用,让我们节省了90%以上的实体声明代码,且表达能力更清晰,可维护性更强

    3.2 类型推断

    1. when(val u = request.session.getAttribute("user")){  
    2.   is String -> println(u.toUpperCase())  
    3.   is Map<*, *> -> println(u["name"])  
    4.   !is List<*> -> println(u)  

    在类型判断代码之后,变量会自动转换为需要的类型,省去变量声明和类型转换代码。

    除此之外Kotlin拥有着丰富的集合操作,以及类扩展、更简洁的lambda、字符串模板、操作符重载、解构等等几乎包含了现在语言的所有优良特征,让业务代码节省50%以上,且表义能力更强。

    4. Spring的加成

    Spring-Boot已经将kotlin提升为仅次于Java的推荐语言,足以见得Spring对Kotlin重视

    4.1 构造函数注入

    Spring构造函数注入配置kotlin的主构造函数语法,Spring+Kotlin天生就很搭

    1. @Service 
    2.  class SMSService(  
    3.   @Value("\\${sms.appId}") private val appId: String,  
    4.   @Value("\\${sms.appSecret}") private val appSecurity: String,  
    5.   private val webClient: WebClient  
    6. ) {  
    7.   //...  

    4.2 针对Kotlin的扩展

    Spring中还有部分专门针对Kotlin的扩展如: SpringApplicationExtension:

    1. @SpringBootApplication  
    2. class SpringBootApplicationStarter  
    3. fun main(args: Array<String>) {  
    4.   //拉起Spring-Boot应用  
    5.   runApplication<SpringBootApplicationStarter>(*args)  

    4.3 针对Kotlin的示例代码

    Spring文档专门为Kotlin编写了示例代码,这可是Spring支持的老牌语言Groovy多年都没享受到的待遇:

    Kotlin天生对Spring的友好,以及Spring对Kotlin的重视和加成,可以预见Kotlin未来在服务器开发领域的地位会越来越高。

    选择Kotlin需要考虑的问题

    1. IDE&工具链

    Kotlin首选开发工具非同属jetbrain公司的IDEA莫属

    Eclipse+Kotlin Plugin也是不错的选择

    二者Kotlin的开发体验都不输于Java的开发体验

    不过Eclipse的Kotlin插件存在两个问题:

    1) Debug不能自动识别Kotlin的main方法,需要手工填入Class名;

    2) Debug不支持Kotlin编译的allopen选项,所有Bean和Configuration类型须显示声明为open

    1. @SpringBootApplication  
    2. open class SpringBootApplicationStarter  
    3. @Configuration  
    4. open class BeanConfig  
    5. @Service  
    6. open class TestService  
    7. @RestController  
    8. open class TestController 

    在IDE之外,Kotlin能完美地运行于Maven和Gradle之上,CI/CD工具Jenkins自然不在话下,JUnit、Swagger等Java原有工具链运行起来都没有差别,在字节码和jar包之外的各种工具能否运行,理论上并不取决于项目代码是Kotlin还是Java;可能最大的问题在于,原有的代码检查工具不能再继承使用。

    2. 编码规范

    Kotlin放开了很多限制如:

    •  可以声明全局变量和全局方法
    •  一个文件可以有多个类
    •  可以通过扩展来为已有类增加新方法

    这带来了诸多便利,如同一领域的实体可以声明在一个文件中:

    1. //WebResponses.kt  
    2. open class WebResponse(var errorCode: Int = 0, var errorMsg: String? = null)  
    3. open class DataResponse<T>(var data: T?) : WebResponse()  
    4. open class MapResponse<K, V>(map: Map<K, V>) : DataResponse<Map<K, V>>(map) {  
    5.     constructor(vararg pairs: Pair<K, V>) : this(mutableMapOf(*pairs))  

    可以很方便地扩展已有类库

    1. // StringExtensions.kt  
    2. val REGEX_Digits = Regex("\\\\\\d+")  
    3. fun String.isDigits(): Boolean {  
    4.     return this.matches(REGEX_Digits)  
    5. }  
    6. // ACLInterceptor  
    7. val appId = request.getParameter("appId")  
    8. if (!appId.isNullOrBlank() && appId.isDigits()) {  
    9. //...  

    但是如果不加限制,很难保证开发人员不随心所欲。所以应该针对Kotlin项目制定一些编码规范(可以与Android项目共用),如:

    •  只有数据实体可以定义在同一个文件中
    •  类型扩展应以”Extensions“后缀结尾,如"StringExtensions"
    •  业务逻辑不应出现在全局方法中,应按照一类一文件的方式组织
    •  业务逻辑的扩展,不应使用Kotlin类型扩展机制,而应使用接口、抽象类、子类

    3. 人员技能

    对Java开发人员来说,学习Kotlin并不是什么困难的事情,以我个人的经验,直接以一个小项目开始的情况下,一周之内编码效率就会明显超过Java。

    当然,语言工具不是项目成败的决定性因素,使用工具的人才是,不妨先让项目组中的核心成员去了解下Kotlin,再做决定。

    快速上手建议从Koans开始,想要更深入地掌握,还是要阅读Kotlin官方文档

    4. 依赖大小

    在Spring项目中使用Kotlin会使依赖增加4.18MB左右

    这在Spring-Boot兴起之前,对我来说确实是个问题,6年前一个war包也就3~5MB大小,还要为Tomcat能部署多个服务而不会代码空间溢出,把war包进一步瘦身——把公共jar包都放入Tomcat/lib之下。

    而现在Spring-Boot项目动则30MB起步的大小,让我已经不再考虑服务器资源的问题,而更享受各种组件和工具带来的开发效率提升。

    当然这个问题,因项目实际情况而异,需要交给作为架构师的你自己去决策。

    5. BenchMark

    具体细节本不是此文的讨论目标,且对Kotlin做BenchMark性能测试不是那么困难,但是Kotlin的BenchMark资料也不多,这里我附送一个简明教程:

    •     步骤1:使用maven生成Kotlin benchmark工程 
    1. mvn archetype:generate \\  
    2.           -DinteractiveMode=false \\  
    3.           -DarchetypeGroupId=org.openjdk.jmh \\  
    4.           -DarchetypeArtifactId=jmh-kotlin-benchmark-archetype \\  
    5.           -DgroupId=org.sample \\  
    6.           -DartifactId=test \\  
    7.           -Dversion=1.0 
    •     步骤2:编写测试代码 
    1. @BenchmarkMode(Mode.Throughput)  
    2. @OutputTimeUnit(TimeUnit.MILLISECONDS)  
    3. open class MyBenchmark {  
    4.     @Benchmark  
    5.     fun testMethod1() {  
    6.         //...  
    7.     }  
    8.     @Benchmark  
    9.     fun testMethod2() {  
    10.         //...  
    11.     }  
    •     步骤3:编译&运行 
    1. mvn clean package  
    2. java -jar target/benchmarks.jar 

    更多性能测试详情,可参考

    •     OpenJDK:jmh官网
    •     kotlin-benchmarks项目

    哪些项目类型适合使用Kotlin

    1.     现有Java项目,不推荐;
    2.     小型或验证型项目,强烈推荐;
    3.     微服务项目,推荐先局部试点,逐步完善编码规范和人员技能;
    4.     大型单体项目,有较强较稳定的核心团队时推荐,人员主靠招聘时不推荐;
    5.     公共组件,不推荐,除专门用于Kotlin的组件外。

    结语

    不管怎么说,语言工具不是项目成败的决定性因素,使用工具的人才是,不妨先让项目组中的核心成员去了解下Kotlin,再做决定。

    编程语言是人类控制电脑的手段,所以绝大多数编程语言都试图使这个过程更加强大和简单。也正因此这个领域诞生了数百种编程语言,不过其中许多编程语言目前已经不再使用,还有一些可能在未来几年里会过时,然而还有很多语言将不断发展,在目前以及未来都占有重要的位置。

课课家教育

未登录