首页
Preview

作为 PHP 开发者,你是否应该尝试使用 Kotlin 呢?

距离我决定从 PHP 切换到 Kotlin 已经一年了。我想和你分享一下我个人对这种变化的感受。此外,我还想在寻找新机会时,向你推荐 Kotlin 作为可能的选择。

PHP 是一种糟糕的语言吗?

当然不是。最新的 PHP 发布(8.0,8.1)引入了很多好东西,比如属性、在构造函数中定义属性、匹配表达式、空安全操作符、枚举和“never”返回类型。当我切换到 Kotlin 时,所有这些东西都是缺失的。

对我来说,PHP 的主要问题不是代码风格和代码结构,这些东西现在几乎都很相似:

正如你所注意到的,PHP 开发人员可能受到基于 JVM 的语言的启发。切换到 Kotlin/JVM 有哪些潜在的好处呢?

简洁的语法

在 PHP 中进行数组或字符串操作的人都知道这种痛苦。你需要学习很多不总是自我描述的 array_* 和 str* 和 str_* 函数。看一下下面的一些示例。

在 PHP 中操作数组和字符串的示例

<?php

// ARRAYS
$array = [1,2,3,4,5,6];

// we need to use count function to get array length
$arrayLength = count($array);

// we need to combine 2 functions to get reversed array with elements multiplied by 2
$multipliedArrayReversed = array_reverse(array_map(fn(int $value): int => $value * 2, $array));

// take a look at filtering - array_filter has reversed function arguments compared to array_map
$oddsOnly = array_filter($array, fn(int $value): bool => $value & 1);

// STRINGS
$string = "Hello World";

// To get only second word of the sentence we can use 2 ways (there are more, but here are 2 examples)
$onlySecondWord = substr($string, strpos($string, ' ') + 1);
// OR
$onlySecondWord = explode(' ', $string)[1];

// another function to reverse the string
$reversedString = strrev($string);

// to check if the string starts with another string we use str_starts_with
$startsWithHell = str_starts_with($string, "Hell");

// but if we want to check if string contains another string arguments are in reversed order
$containsHell = str_contains("Hell", $string);

现在,让我们将其与 Kotlin 中的代码进行比较,并判断哪个更易读。

在 Kotlin 中操作数组/列表和字符串的示例

fun main() {
    //ARRAYS
    val array = arrayOf(1,2,3,4,5,6)

    // array size is just a property not another function
    val arrayLength = array.size

    // methods are chained and lambda function can be used
    val multipliedArrayReversed = array.map { it * 2 }.reversed()

    // the order of arguments is consistent
    val oddsOnly = array.filter { it % 2 != 0 }

    // STRINGS
    val string = "Hello World"

    // there are many helpful methods included comparing to Java
    val onlySecondWord = string.substringAfter(' ')
    
    // just another method to reverse the string
    val reversedString = string.reversed()

    // no need to remember about the arguments order
    val startsWithHell = string.startsWith("Hell")

    // again - no need to remember about the order
    val containsHell = string.contains("Hell")
}

Kotlin/JVM 还支持 PHP 中不可用的东西,比如泛型扩展。两者都非常有用,可以使代码非常干净和可重用。

函数组合中的差异

首先让我们看一下在 PHP 和 Kotlin 中编写的 fold 函数

Kotlin 中的 fold

fun <T, R> Collection<T>.fold(
    initial: R,
    combine: (acc: R, nextElement: T) -> R
): R {
    var accumulator: R = initial
    for (element: T in this) {
        accumulator = combine(accumulator, element)
    }
    return accumulator
}

PHP 中的 fold

<?php

function fold($array, $callback, $initial=null)
{
    $acc = $initial;
    foreach($array as $a)
        $acc = $callback($acc, $a);
    return $acc;
}

PHP 不支持类型/泛型集合。当你遍历数组时,你永远不知道里面有什么样的值。在函数式编程中,这可能是一个问题,特别是如果你想编写一个特定类型的高阶函数。

在 PHP 中,你无法传递回调函数的结构。这使得开发更加困难,因为 IDE 无法解析它。而且,由于没有泛型类型的支持,编写函数本身也更加困难。

最后一件事是,PHP 没有扩展函数的支持。它强制你总是编写一个独立的函数,不能与特定的类或接口连接。

多模块项目

JVM 环境和工具有更好的支持,可以组织多模块项目中的代码。在 PHP 中,为了避免在另一个模块中使用一个类,你唯一能做的就是编写一些体系结构测试,以检查命名空间的误用。在 Kotlin 中,你可以指定该类是内部的,这使它只在一个模块中可见。

从工具的角度来看,Gradle 和 Maven 提供了多模块项目的开箱即用支持。你可以启用不同的插件并要求每个模块的依赖项。我在使用 PHP Composer 时不记得有这种可能性。

性能

我目前维护的服务的 99 百分位响应时间约为 6 毫秒(在 Spring Boot 2.6.x 上运行)。在 PHP 和 Symfony 框架中实现这种性能非常困难,在大多数情况下几乎不可能。我在 PHP 中的个人最佳成绩是一个类似规模的项目的每个请求的平均响应时间为 30 毫秒。

PHP 是一种解释性语言。这意味着为每个单独的请求创建应用程序实例化。这也意味着请求无法异步处理——没有办法使 Nginx 服务器暂停,因为它需要等待结果。PHP 现在提供了 JIT 和预加载,但结果无法与 JVM 堆栈相比。

Kotlin 通过使用协程提供了易于实现的异步请求处理。坦率地说,即使没有任何调整,它也可以提供明显的结果,并将可以使用相同数量的资源处理的请求数量增加。

对于 JVM,还有另一种提高性能的可能性——GraalVM,高性能 JIT 编译器。这是一种可以用相对较低的努力使用的东西,带来了巨大的好处。此外,Spring 正在开发对 GraalVM 的官方支持——Spring Native

官方库的可用性

PHP 在全球范围内得到的官方支持不太好。通常,在寻找 SDK/库时,它不是从官方来源提供的。这意味着质量也值得怀疑。项目有时会突然被放弃而没有替代品。有时我被迫 fork 仓库并自己添加一些必要的修复。更重要的是,PHP 很多次错过了技术的库。

Kotlin 可以使用 Java 库。Java 得到了很好的支持——我知道的几乎所有公司/项目都会为 JVM 发布官方 SDK/库。更重要的是,许多库直接支持 Kotlin。这带来了很多稳定性,并使项目更容易维护,而不必担心突然失去任何依赖项。

通信协议支持当你使用微服务时,通信方面至关重要。使用错误、慢的协议可能会引起很多问题。只需要一个瓶颈就足以拖慢整个架构。我喜欢使用 gRPC 作为(微)服务之间的通信协议。它提供了不错的性能。使用 Protobuf 模型,你可以轻松地为每个微服务定义 API 合同。还有其他选项,比如 SOAP 或 Java RMI,但现在它们已经不常用了,所以我不会深入研究它们。

不幸的是,PHP 对 gRPC 的支持不太好。你可以生成一个客户端,但运行 gRPC 服务器很难(或不可能)实现。此外,并非所有的 Protobuf 语法都受 PHP 生成器的支持。有人可能会说,我们仍然可以使用 OpenAPI。不幸的是,与 gRPC 和 Protobuf 相比,它是一种性能更差的解决方案,并且在长期运行中更难处理。这里有一个对这两种协议的很好的比较。

消息代理支持

在 PHP 中使用 Symfony,从类似 RabbitMQ 或 Kafka 的代理中消费消息,需要运行一个与主应用程序流分开执行的命令。此外,你需要设置一个适当的监管机制来保持其永久运行。现在将其乘以要从中消费消息的队列/主题的数量。在 JVM 中,可以通过多线程轻松实现。应用程序作为进程运行,并且可以访问 CPU 核和线程。你可以分配一个线程(或更多)并在同一个应用实例中运行消费者。编写基于事件的通信更容易实现和测试。你还可以考虑创建一个仅执行消息消费的模块。

我在 PHP 中想念 Symfony Messenger 库。我找不到任何类似的 Java/Kotlin 项目,可以为各种消息代理或数据存储提供这种易于配置的发送事件功能。它还允许设置重试/死信策略并发送到多个目标。

创建分离的模块/库

PHP 有 Composer,JVM 有 Maven/Gradle。在 Composer 中创建和管理库不是那么直观。我认为在 JVM 世界中更容易,特别是在使用 GitHub Actions 和 GitHub Packages 时。你只需要在工作流程中使用一个步骤。

- name: Publish artifact
  uses: eskatos/gradle-command-action@v2
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN_WITH_WRITE_PACKAGES_PERMISSION }}
  with:
    arguments: publish

该软件包会自动附加到正确的存储库,并且可以在同一组织内的其他项目中使用。你只需要在 Maven/Gradle 文件中添加一个存储库。不幸的是,PHP 不提供这种支持。

另一个重要的事情是本地开发。使用 mavenLocal() 存储库和 publishToLocalMaven 任务,你可以轻松地实时测试你的更改。在 PHP 世界中,你需要添加一个特定的存储库,指向一个本地目录,然后每次更改内容后运行 composer update 操作。

可观察性

在我的工作中,我们花了很多精力来实现可观察性。我们的基于 JVM/NodeJS/Ruby 的服务使用 OpenTelemetry 标准来收集数据并将其发布到 HoneyComb。目前 PHP 对此的支持仅处于预 alpha 阶段,不应在生产环境中使用。这是分布式跟踪领域的一个巨大劣势。由于无法正确应用遥测,我们受到 PHP 服务的限制,我们能观察的范围有多远。我们仍然可以使用 NewRelic 等工具,但如果比较价格,它的价格要高出几倍,而且功能也不完全相同。

IDE/调试支持

Kotlin 和 IntelliJ 都由 JetBrains 开发。因此 Kotlin 的 IDE 支持几乎是完美的。它不仅始终支持最新的语法,而且与 JVM 工具/框架(如 Gradle、JUnit 和 Spring Boot)集成得非常好。

最明显的区别是调试集成。如果你想本地或远程调试 Java 代码,它就可以工作。对于本地环境,你不需要设置或启动任何其他内容。对于远程环境,你只需要提供一个调试端口。此外,Java 中的调试器对性能的影响要小得多——应用程序启动稍微慢一些,但后来几乎没有任何区别。

对于 PHP 的 XDebug 扩展,有一篇包含所有步骤的完整文章。你可能已经注意到,它非常多。不久之前,使用 XDebug 工作通常意味着如果没有启用调试器,则无法启动 Symfony 框架的缓存。我听说随着 XDebug 3 的改变,情况有所改善,但我没有测试过。一年前,PHP 调试是一种痛苦的体验。

多平台代码

Kotlin Multiplatform 是 Kotlin 的一大优势。

你可以创建一个多平台库,包括通用代码和其平台特定的 JVM、JS 和本地平台实现。一旦发布,多平台库可以作为依赖项在其他跨平台项目中使用。(引自 Kotlin 官方文档)

在我的公司中,有几个库在移动平台上都在使用。在 PHP 世界中没有替代品。

然而,由于 Kotlin 不仅是针对后端的语言,像 PHP 那样,它也有一个缺点。当你尝试在互联网上找到合适的解决方案时,你将需要浏览所有通常只与移动世界相关的文章。

框架/库支持

这可能是我最惊讶的一点,但我不得不说 PHP 框架的文档比 Java/Kotlin 的文档好得多。在使用 Symfony 时,几乎所有的问题都可以通过查看官方文档和示例来解决。对于 Kotlin,我有时需要花费几个小时来找到更复杂情况的合适解决方案。也许这是因为我只使用了 Spring Boot 和一点 Ktor。对于其他框架来说,情况可能更好?如果你关心领域模型和基础架构的清晰分离,你可能会发现像PHP中的Doctrine那样在单独的XML/YAML文件中定义数据库映射很困难。注释很可能是唯一的方法。同样,这也适用于验证——将其编写到单独的文件中并不容易。

在使用ORM时,我也错过了关于数据库迁移的一件事。Doctrine提供了自己的迁移库。它允许从实体映射自动生成迁移脚本。这非常方便,特别是当你开始做一个新项目时,它经常发生。Hibernate不支持它。你可以通过使用外部库来实现它,但这并不容易,很可能会花费你几个小时的时间。

作为PHP开发者,你应该尝试Kotlin吗?

我提到了我已经经历过的事情,并考虑了实际尝试从PHP转向Kotlin/JVM的关键因素。我的感觉是积极的。我永远不会决定回到PHP。使用Kotlin工作给我带来了很多乐趣,这是我在近15年的PHP工作中失去的。

那么对于这个问题,我的一般答案是什么呢?我认为这取决于具体情况。

你需要知道学习曲线很高。从PHP转向Kotlin会迫使你以不同的方式思考你的应用程序和开发。你需要学习并照顾并发性、并行性、垃圾回收等问题。你需要了解Gradle/Maven来配置你的项目。你需要找到并熟悉一个新的Kotlin/Java框架。你需要知道有时你也需要涉及一点Java,特别是当使用还没有Kotlin版本的库时。

如果以上事项并没有吓到你,那么你就可以尝试了。我强烈建议从JetBrains Academy Kotlin Basics Course开始。这里的项目组织得非常好,通过完成这些项目,你可以学习到语言的基础知识。下一步最可能是尝试选择一个框架,比如Spring Boot,创建一个小型的后端项目。之后,如何继续只取决于你。如果你的公司支持你过渡,那是最好的,但我相信任何人都可以通过一些热情和私人时间的牺牲来进行转换。

译自:https://towardsdev.com/should-you-try-kotlin-as-a-php-developer-d3c678f5e8e5

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
anko
宽以待人处事,严于律己修身。

评论(0)

添加评论