• Java 默认可变性:“万亿美元级别的错误” | InfoQ 访谈


    James Ward 是谷歌的 Kotlin 产品经理,同时也是 Java Champion。他和 Bruce Eckel(《Java 编程思想》作者)一起主持“快乐之路编程”播客。在其中一集播客中,Eckel 谈到了“仍被困在 Java 世界中的人们”。Ward 对此表示同意,并将 Java 的默认可变性称为“万亿美元级别的错误”(参考了 NullPointerException 是“十亿美元级别的错误”的说法)。Ward 对 Java 的看法引起了 InfoQ 的好奇。InfoQ 采访了他,以下是采访内容。

    InfoQ:Kotlin 产品经理在谷歌是一个怎样的角色?你的日常职责是什么?

    Ward:Kotlin 产品经理的工作有个方面。我与我们的 Android 团队和 JetBrains 一起致力于 Kotlin 语言的改进和发展。我还与谷歌的许多工程团队合作,帮助他们成功地将服务器端和 Android 从 Java 迁移到 Kotlin。

    InfoQ:2022 年 JVM 编程语言处在一个怎样的状态?

    Ward:我在 25 年前开始使用 Java,并且认为它仍然是一种很棒的语言。但在过去的十年中,我也使用了更现代的 JVM 语言,包括 Scala 和 Kotlin。对于那些想要将函数式编程发挥到极致的开发人员来说,Scala 非常棒。对于现有的 Java 开发人员来说,Kotlin 更像是一个循序渐进的步骤,它可以很好地支持一些函数式编程范式,比如不可变性,但缺乏 Scala 的“Monadic”特性。Kotlin 和 Scala 都与 Java 有很好的互操作性,支持生态系统共享。JVM 开发人员对于优秀的编程语言有很多选择,工具也有很多重叠的地方(构建工具、IDE、生产/内省工具等)。在 JVM 这个大生态系统中有这么多语言选项是非常棒的。

    InfoQ:现在有多少 JVM 后端开发在使用 Kotlin?你认为 Kotlin 怎样才能在后端开发中变得更受欢迎?

    Ward:总的来说,JVM 服务器端的 Kotlin 份额相当小,但它正在迅速增长。例如,谷歌已经见证了 Kotlin 用于服务器端开发的显著增长。从 Java 转向其他语言或来自其他语言的开发人员都表示,他们对这种体验非常满意。Null 安全性、协程和可表达性通常被认为是 Kotlin 更有效率、使用起来更有趣的重要因素。

    Kotlin 肯定会在服务器端继续发展。Spring、Quarkus 和 Micronaut 已经做了相当多的工作,让 Kotlin 的使用体验变得非常棒(例如为 Reactive 提供协同程序互操作性)。企业对新技术的采用通常非常缓慢。尽管如此,对于许多人来说,迁移到 Kotlin(在 JVM 上)比迁移到 Rust 的风险和破坏性要小得多。此外,Kotlin 与 Java 的互操作性有助于进行增量式的代码迁移,而不是进行重写。我参与过的许多团队只是将 Kotlin 新代码添加到现有的 Java 代码库中。

    InfoQ:JVM 编程语言与其他编程语言相比如何,尤其是在云环境下?Python 和 JavaScript 在谷歌搜索中出现的频率比 Java 更多,在 Stack Overflow 上的相关问题也更多。

    Ward:如果我要设计一种编程语言,我希望对语言的搜索和提问可以少一些。作为一名具有 30 多年经验的程序员,我更倾向于使用那些能让我写出更“正确”、更可测试和可重用代码的语言。我不喜欢在生产环境中出现意外,我希望能够毫无畏惧地进行重构。拥有一个可以对程序执行高级验证的编译器是一个基本特性。当然,我可能不能像其他人那样快速地编写代码,但至少可以写出在生产环境中不太可能有 bug 的代码。如果你将修复生产 bug 作为开发周期的一部分,那么使用更现代的 JVM 编程语言可能可以解决许多问题领域提供最高的生产力。

    InfoQ:GraalVM 生成的原生 Java 应用程序启动更快,使用更少的内存。这将如何影响 JVM 语言在云计算中的竞争地位?

    Ward:启动速度和内存开销确实阻碍了 JVM 技术在某些问题领域(无服务器、CLI、Kubernetes Operator 等)的采用。GraalVM 原生镜像、Kotlin/Native 和 Scala Native 帮助把这些语言带到以前更适合解释语言或原生语言的地方。现在,我们既有了我们的蛋糕(快速启动和最小的内存开销),也可以吃掉它(现代高级语言)。我最近用 Kotlin Ktor 库创建了一个服务器,我可以在 JVM 上运行它,或者用 Kotlin/Native 和 GraalVM 原生镜像编译成原生程序,启动时间大约为 2 毫秒,内存使用量为 11MB,二进制大小被压缩到 700KB。对于许多场景,我们不再需要在原生语言和现代/高级语言之间做出权衡。

    InfoQ:Scala 为什么如此吸引你?

    Ward:Scala 的学习曲线很长。学这门语言十多年了,我还是觉得自己像个新手。这对我来说很棒,因为我喜欢学习新的编程语言,并享受挑战。我也确实接受了函数式编程的许多原则,但还没有完全像 Haskell 那样,所以 Scala 是一个适合使用基于 JVM 的函数式编程的好地方。我目前正在和 Bruce Eckel、Bill Frasure 一起写一本使用 Scala 3 和 ZIO 2 进行面向副作用编程的  。函数式副作用是一种概念,它对我们创建的软件质量有很大的影响,但在 Java 或 Kotlin 中还没有得到很好的支持。选择 Java 或 Kotlin 有很多原因,但像副作用这样的概念并不在其中。

    InfoQ:Scala 最适合哪些应用或解决哪些问题?不适合哪些场景?

    Ward:有许多因素决定了技术的契合度。在我最近从事的项目中,团队结构决定了 Java 和 Spring Boot 是最适合这种环境的。“适合”的最重要的一个因素是团队想要使用这些技术。我认为这与我的 Kotlin 项目经理工作目标是一致的,就是帮助开发人员使用 Kotlin。

    InfoQ:JetBrains 声称 Kotlin 是“更好的 Java”,但是 Java 的受欢迎程度仍然是它的 5 到 12 倍。你认为 Kotlin 今天的角色是怎样的?在未来会变成什么样?

    Ward:Java 语言一直在发展。但正如 Java 语言架构师 Brian Goetz 所描述的那样——它有“定局者优势”,在许多情况下都是正确的选择。如果说创业公司的口号是“快速行动,打破现状”,那么企业的口号则是“缓慢行动,什么都不打破”,这与 Java 语言在过去 20 年左右的发展是一致的。对我来说,我喜欢比典型的企业更快地发展(由于合规性、安全性、监管和其他原因)。所以,Kotlin 在某种程度上是“更好的 Java”,但对我来说“更好”并不意味着对每个人都“更好”。JVM 和 Android 同时支持 Java 和 Kotlin——这是一件好事。

    InfoQ:在“快乐之路编程”播客的 第51集 中,Bruce Eckel 谈到了“仍然被困在 Java 世界中的人们”(大约在 33:15),你表示了赞同。请你解释一下为什么你和 Bruce 会这么认为。

    Ward:25 年前,我写 Java 就像写 Perl 一样。Bruce 的《Java 编程思想》改变了我对编程的看法。现在,Bruce 和我都发现编程范式和经验具有相同的效果,完全改变了我们思考事物的方式。Bruce 和我没有在 Java 中体验过这种颠覆,但在其他语言中有。我相信 Bruce 和我在那一集中所强调的并不是对 Java 的抨击,而是希望程序员能够不断学习并找到成长方法,就像 Bruce 和我所做的那样。

    InfoQ:正如 Tony Hoare 所说的那样,NullPointerException 是“十亿美元级别的错误”。语言中的 Null 安全性是对这个错误的解决方法。Kotlin 有,Dart 和 Swift 甚至有 全面Null安全性 (Sound Null Safety),但 Java 没有,而且似乎在短时间内不会提供。你认为这是为什么?

    Ward:在 Java 中,所有不是原始值的值都是可空的。要改变这一点,需要对整个语言和标准库进行大规模的改革。许多现代语言都有一个基本原则,即通过类型系统来表达可空性。这在后期是非常难或不可能实现的。如前所述,我希望能够编写出在编译时可验证的程序,而显式的可空性是实现我的目标的一种方法。我不再经常使用 Java 语言的原因之一是很难通过编译器验证的方式来表达可空性。

    InfoQ:你说在未来添加 Null 安全性是“非常困难或不可能的”。但 Dart 在发布 1.0 版本 7 年后为应用程序和库添加了 Null 安全性。不管怎样,你对希望减少 NullPointerException 的 Java 开发人员有什么建议?

    Ward:最难的部分不是语言特性,而是 API。Java 标准库中的所有东西以及 Java 库生态系统中的大部分东西都是可空的。要让 Null 安全性变得有用,并且处理起来不那么麻烦,所有基础类型必须正确地表达它们的可空性。这是一个很难或不可能实现的变化。

    InfoQ:在同一集“快乐之路编程”播客中,你称 Java 的默认可变性为“万亿美元级别的错误”(大约在 35:05)。请详细说明你为什么这样认为。

    Ward:我遇到过很多次生产问题,因为无法“推理”出问题代码,所以很难找出原因。当人们在 Twitter 上发布类似“这段代码是做什么的”这样的问题时,你就会明白。大多数时候这都是一个谜,因为可变性让我简单的大脑无法推断发生了什么。但你永远不会看到人们以一种纯粹不可变的形式发布同样的问题,因为我们的大脑能够理解不可变的值和纯粹的函数。我确信,我们的程序越能纯粹地实现函数和值,我们构建的程序中出现的 Bug 就越少。

    InfoQ:你认为工具支持(比如 IDE 和构建工具)对一门编程语言取得成功有多大影响?

    Ward:我曾经在没有 IDE 帮助的情况下(vim 算不算)写了很多代码,但现在 IDE 成了我工作效率的重要组成部分。我之所以喜欢提供了优秀的类型系统的编程语言,其中一个原因是 IDE 在我编写代码时提供了更好的提示。当我不得不用动态语言编写代码时,我想知道如果没有那么多 API 可供参考,谁能完成他们想做的事情?如果没有 IDE 的提示功能,我就什么都做不了。

    InfoQ:说到工具,Visual Studio Code 在 2021 年 2 月拥有 1400 万用户,在 Stack Overflow 的“2022 年开发者调查”中是第二受欢迎的 IDE(第一名是 neovim)。我们假设 Visual Studio Code 成为所有开发人员的默认免费 IDE,并且支持所有相关的编程语言和框架,那么这将会如何改变软件开发?

    Ward:VS Code 是一个很棒的工具,对于许多开发人员来说,它已经比“Sublime Text”、vim 或 emacs 向前迈出了一大步。但是对我来说,它仍然没有 IntelliJ(对于 JVM 的东西)那么有用,特别是在涉及到重构时。所以,我不经常使用它,但我知道它可能是开发人员使用过的最好的代码编辑器(假设他们没有使用过所有的代码编辑器)。

    InfoQ:编译器可以暴露代码错误。静态分析器,如 Error Prone、Spotbugs 或 PMD,会显示更多的错误,包括可怕的 NullPointerException。为什么这些静态分析器没有得到更广泛的使用?

    Ward:一般来说,我喜欢让我的工具链尽可能精简。无论是出于性能、简单性还是协作性的考虑,我更喜欢将尽可能多的验证逻辑放入编译器可以验证的东西(即类型系统)中。对我来说,检查器和静态代码分析工具可以验证的东西都可以放在编译器中验证。不过,语言的限制可能阻碍了这一点。这些工具有助于提高代码质量,但同时也向语言设计者发出了一个强烈的信号,告诉他们应该如何从元编程转向单纯的编程。

    InfoQ:你说你“喜欢把尽可能多的验证逻辑放入编译器可以验证的东西中”。Java 编译器不验证可空安全性、空 else 分支等,但是像谷歌 Error Prone 这样的静态分析器会这么做。你如何看待将这些分析器添加到 Java 中所带来的好处与它会让工具链复杂化的问题?

    Ward:分析器和其他静态代码分析工具暴露了类型系统和编译器检查的局限性。这些限制会一直存在,所以这些工具不会很快消失。但希望它们能够有助于编程模型和编译器的发展,随着时间的推移能够涵盖更多可能的问题。

    InfoQ:谷歌的跨平台 UI 框架 Flutter 编译一个变更和更新应用程序只需要不到一秒的时间。相比之下,为什么编译和更新 JVM 应用程序仍然如此缓慢?

    Ward:编译器要验证的东西越多,花费的时间就越长。我可以写一些零编译的代码,在生产环境中运行它,然后在运行时发生错误。这不是我想要的软件开发方式。所以对我来说,编译时间必须与编译器的价值相平衡。不过,我确实经常借助热加载(感谢缓存)在一秒钟内用增量的方式运行 Java、Kotlin 和 Scala 编译器。这种争论应该从“需要多长时间将包含不确定数量问题的东西部署到生产环境”变成“需要多长时间来纠正或消除错误”。

    InfoQ:在我的 Spring Boot 项目中,频繁的类重载失败抵消了编译速度。关于你的“将包含不确定数量问题的东西部署到生产环境”,我认为 Dart(Flutter 使用的语言)的编译复杂性可能与 Java 差不多。尽管如此,大多数情况下,Flutter 在移动设备上重新编译和部署只需要 1 秒钟,但大多数 Java 项目都做不到。现在,Flutter 有了它的整个工具链(编程语言、编译器、运行时、框架和构建工具),但 Java 还没有(例如,构建工具和应用程序框架)。对于开发人员的生产力来说,JVM 语言拥有整个工具链有多重要?

    Ward:没有什么能阻止 JVM 的内部开发周期变快。Android Studio 有一个叫作 Live Edit 的新功能,可以基于代码变更即时更新模拟器或设备上的 Jetpack Compose App 的 UI。十年前,Play Framework 就已经通过利用一些奇特的类加载器技巧实现了 JVM 服务器的亚秒级重载。主要的挑战在于如何投入工程时间让体验变得又快又好。但由于某些原因,这并不是 JVM 生态系统的首要任务。对于服务器框架来说,Quarkus 在优化方面做得最好,我相信他们还可以做更多的工作。

    InfoQ:你如何定义和衡量一门编程语言成功与否?例如,你可以说 Scala 是成功的,因为它让函数式编程变得更加主流。你也可以认为 Scala 不再成功了,因为它把第二大 JVM 语言的位置让给了 Kotlin。

    Ward:目标很重要,每个人都有不同的目标。对我来说,这与价值定位有关。我非常欣赏 Flix 把它的 目标/原则 写了出来。

    Flix 是一门非常成功的编程语言,因为它在实现目标方面做得非常出色。如果 Flix 设定的目标是拥有 1000 万活跃的开发人员,那么它肯定已经失败了(但我仍然喜欢它,因为我赞同它的原则)。喜欢一门语言和成功掌握一门语言是两码事。作为一名 Kotlin 项目经理,我的目标之一是让开发人员可以更容易地构建正确的软件(即更少的 Bug)。Kotlin 已经被证明可以减少 20%的 Android 应用程序崩溃,这是一个巨大的成功。我想更进一步,通过语言和工具的改进继续减少应用程序和服务器端错误。

    InfoQ:软件开发的历史就是抽象层次不断增加的历史,面向对象和函数式编程已经有 50 多年的历史了。你认为在过去的 20 年里,抽象层次是如何提升的?你如何看待未来 20 年的增长?

    Ward:直到最近,函数式编程的许多想法仍然被局限在为数学极客(我希望有一天也能成为这样的人)提供的技术中。现在,多亏了 Scala(和其他语言),我们开始看到面向对象和函数式编程的融合,使得那些非数学极客也能使用函数式编程。这种融合将会持续一段时间,帮助我们的代码变得更加可测试和可重用。Kotlin 就是一个很好的例子,它是许多 Java 面向对象开发人员通往“轻函数式编程(lite-FP)”的桥梁,而“lite-FP”并不要求开发人员掌握范畴论。这一转变的下一阶段是拥抱“副作用”理念(将纯粹的函数与不具有参考透明性的部分分离开来)。许多新的编程语言已经内置了这个概念——Flix、Unison、Roc 等。除了副作用之外,我们可能会看到类似 Datalog 的概念——一种内置在通用语言中的查询语言。我在 Linq 中第一次了解到这种想法,然后是 Flix。查询是一种非常普遍的需求,无论是对于数据库、透镜(更新不可变的数据结构)还是 GraphQL 等。因此,使用集成的、经过编译器验证的方式编写查询是一个显著的优势。

    InfoQ:哪一种编程语言发展得最好?

    Ward:这取决于“最好”是怎么定义的。如果我们从纯粹的学术角度来考虑这个问题,我认为有关 Scala 的学术研究比我所知道的任何一种语言都多了很多个数量级。Scala 的许多特性最终会出现在其他语言中,这对每一个人来说都很有益。Python 已经做了惊人的工作,成为一种普遍可接受的语言。我听说许多面向数据的专业人士无法解决大多数典型的编程挑战,但可以使用 Python 编写复杂的数学算法,或使用 Pandas、NumPy 等库处理大量的数据集。Kotlin 是一种具有 Java 互操作和多平台功能的现代语言。所以什么是“最好”取决于很多因素。

    InfoQ:JVM 语言即将到来的哪个新特性最让你感到兴奋?

    Ward:在 JVM 方面,Loom 改变了游戏规则。对于大多数 Java/JVM 开发人员来说,“反应式”编程是一个好主意,但不值得为此承担认知和复杂性方面的负担。Kotlin 协程为异步操作提供了类似的零认知成本的想法。然而,对于许多 JVM 开发人员来说,在 Loom 进入他们的工作环境之前,反应式编程可能仍然是一个“不错”的特性。因此,在此之前,许多基于 JVM 的开发人员将在 JDK 8 上使用诸如 Kotlin 协程和 Scala ZIO Effects 这样的并发抽象。考虑到 Loom 的发布时间线和当前可用的替代方案,我不得不说,在 JVM 语言中,最让我感到兴奋的是即将到来的 Scala 无括号语法,它在 Scala 3.0 中已经完成了一半,并有望在 Scala 3.3 中全部完成。我希望我的代码相对于我要解决的问题来说少很多视觉上的噪音。我知道这看起来很傻,只是把括号拿掉就能产生这么大的影响。但是 Python 告诉我们,在大多数组织中,认知开销通常是最高的成本。编写一个正确的程序最困难/最昂贵的部分不是文本到字节码/机器码的转换,而是以计算机能够理解的形式正确地表示和阅读人类想法的成本。这看起来很愚蠢,但是大多数代码中的括号会分散我的注意力,影响我理解代码的意图。

    InfoQ:如果你可以对每一种 JVM 语言都做一个改变,你会做哪些改变?

    Ward:Java——我想做很多改变,这听起来像是对 Java 的控诉。但这并不是因为 Java 是基于 JVM 的,你也可以选择另一种语言,或者接受 Java 缓慢的发展进度,这都没什么问题。如果必须要改变一件事,那可能是更好地支持不可变性。默认的可变性是导致不确定性程序的根源。

    Kotlin——当我在用 Kotlin 编程时,我最怀念 Scala 的是它的单体链语法(在 Haskell 中叫作“do notation”,在 Scala 中是 for)。与 Kotlin 协程一样,当函数调用被链接起来时,代码看起来就像是命令式的。我不知道该如何将这种东西添加到 Kotlin 中,但如果做得好,我认为这将是非常棒的。

    Scala——Scala 最难的地方在于可以通过多种方式来完成大致相同的事情。例如,在 Scala 3 中至少有三种方法来实现基本的 Sum 类型(sealed、enum 和逻辑 OR)。尽管如此,Scala 的复杂性仍然是一个问题,完成大多数相同的事情有多种方法也是一个问题。

    InfoQ:COBOL 已经有 60 多年的历史了,现在仍然有人在使用它。你认为在 2056 年 Java 60 岁的时候,开发人员还会开发新的 Java 应用程序吗?

    Ward:当然!Java 是我们日常使用的许多系统的关键部分。它不会消失,它会通过缓慢而渐进的增强继续演化(不像 COBOL 那样)。更大的 Java 生态系统(包括 Kotlin、Scala、Clojure、Groovy 等)也在继续增长。作为一个整体,它可能是世界上最大的开发者生态系统。新的 JVM 语言不断涌现,就像 Flix 一样,这表明创新周期不会很快停止。像 Testcontainers、GraalVM 和 Kalix 这样的创新和改变游戏规则的技术继续从 Java 生态系统中涌现出来,这股力量将推动未来 35 年(至少)的增长和进步。

    InfoQ:请你对 2022 年 JVM 编程语言的发展状态做个总结。

    Ward:现在是 Java、Kotlin 和 Scala 的一个激动人心的时刻!这些工具和语言让我体验到最高的开发效率。从移动端到服务器端,Java 技术为我们每天使用的绝大多数关键系统提供支撑。对我来说,能够在一个平台上选择多种不同的编程语言真是太棒了。

    InfoQ:James,感谢你接受我们的采访。

    总结

    Ward 很好地解释了播客上的评论——当 Eckel 谈到“仍被困在 Java 世界中的人们”时,他指的是使用他和 James 眼中的 Java 旧范式(比如过程式和面向对象编程)的开发人员。相比之下,Eckel 和 Ward 在 Scala 中采用了 Java 语言中没有的函数式编程概念。可变性让 Ward 更难理解代码,并最终产生更多的 Bug,这与不可变的函数完全不同。

  • 相关阅读:
    LangChain 开发LLM的框架
    Task Flow使用指南之五:捕获异常
    mysql基本操作增删改查
    蓝桥杯 子矩阵 (找大小为a*b的矩阵的最大最小值的乘积,queue)
    Day17.1:静态与非静态的详解
    【计网】网络层
    免费开源漏扫软件 Nessus
    100 个 Kotlin 面试问题及答案(其一)
    Android11.0默认打开允许安装未知源开关
    最强人工智能ChatGPT引领AIGC发展
  • 原文地址:https://blog.csdn.net/m0_73257876/article/details/126826944