Lombok 在 Java Records 时代依然不可替代

Hacker News Top 新闻

摘要

作者为 Lombok 辩护,认为在 Java Records 时代它依然不可或缺,因为它在日志注解、构造器注入和 Builder 等方面大幅减少样板代码,而这些 Records 并不能替代。文章以 15 年实际使用经验和项目中 Lombok 注解使用统计为据,反驳了 Lombok 引入奇怪 bug 和"暗黑魔法"的常见批评。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/10/19 05:00

# 在 Java Records 时代,Lombok 依然不可替代 来源:https://swaranga.dev/posts/on-lombok/ 每六个月,如同时钟般准时,一个新的 JDK 就会发布——而也就在这个时候,Java 社区的"意识形态纯度检验"便如期而至。那些老面孔又跳出来对 Project Lombok 大加指责。他们声称 Lombok 对 `javac` 编译器做了"奇怪的 hack",说它拖慢了 JDK 升级的步伐,还说我们有了 Records 就不再需要它了。 对于这种对 Lombok 没完没了的"原则性反对",我实在有些厌倦了。我是一名应用开发者,不是编译器工程师。我使用 Lombok 已有大约 15 年的时间,可以明确地说:它的好处远远大于缺点。我很庆幸自己没有和那些持有教条式观点的人共事,因为说到底,我们拿工资是为了交付能运行的软件——而不是去迁就编译器。我依然押注 Lombok,而那些反对它的论点在我看来在实际场景中往往站不住脚。 ## Lombok 会引入"奇怪的 bug" 在我使用 Lombok 的十五年多时间里,我只见过*一次*问题,Lombok 在事后复盘中被指认为根本原因——即便在那次事件中,我认为这个归咎也是不公平的。当时有一个标注了 `@Data` 的类,包含一个基本类型 `boolean` 字段。在从 SQS 反序列化消息时,传入的负载缺少这个字段。由于它是基本类型,Jackson 为其赋了默认值 `false`,从而意外触发了另一条代码路径。事后复盘将其标记为"Lombok 问题"。 **我不同意。** Lombok 只是生成了 setter 方法。真正的根本原因是: 1. **糟糕的数据建模:**该字段在逻辑上需要三个有效状态(`true`、`false` 和 `null`),所以从一开始就应当使用包装类型 `Boolean`。 2. **测试覆盖不足:**这个边界情况本应在测试中立即被发现,或在代码审查(PR Review)中被指出。 把开发者的编码错误和测试覆盖的缺口归咎于 Lombok,实在是太懒惰了。 ## "我们有 Records 了"的迷思 每当你为 Lombok 辩护时,总有人会跳出来说,*"直接用 Records 不就行了!"* Records 对于不可变数据载体来说非常出色,但它们并不能完全替代 Lombok。为了证明这一点,我在当前项目中做了一个快速统计,看看我们实际依赖了哪些 Lombok 特性。以下是原始数据: ``` find . -name "*.java" | xargs cat | grep "import lombok" | sort | uniq -c | sort -n -r 144 import lombok.extern.slf4j.Slf4j; 131 import lombok.NonNull; 120 import lombok.RequiredArgsConstructor; 68 import lombok.Builder; 30 import lombok.Data; 24 import lombok.Getter; 22 import lombok.NoArgsConstructor; 22 import lombok.AllArgsConstructor; 12 import lombok.experimental.Accessors; 8 import lombok.extern.jackson.Jacksonized; 6 import lombok.EqualsAndHashCode; 4 import lombok.AccessLevel; 2 import lombok.Singular; 2 import lombok.experimental.UtilityClass; 1 import lombok.ToString; 1 import lombok.extern.log4j.Log4j2; ``` 占据列表最顶部的并不是 `@Data`。而是日志记录和用于支持依赖注入的代码。我无法想象没有 `@RequiredArgsConstructor` 该怎么写代码。在 Spring 中,你只需将注入的服务声明为 `final` 字段,再加上那一个注解,构造函数的样板代码就消失了。Records 对此完全无能为力。再加上 `@Slf4j` 和 `@Builder`,每个模块你就能省去数百行繁琐且容易出错的样板代码——否则你不得不手写、审查并维护它们。 至于让 IDE 生成 getter 和 setter?你还是得去阅读那些杂乱的代码。而且,添加一个新字段意味着你得记得手动重新生成 `equals` 和 `hashCode` 方法——这一步很容易遗忘。 ## Lombok 的"黑魔法" 纯粹主义者声称 Lombok 的 AST 操作是"魔法",修改编译器的行为是一种架构上的罪过。但这些开发者却欣然地在那些在运行时做着更离谱的事情的框架之上构建整个企业系统。 以 Jackson 为例。Java 纯粹主义者希望一切都显式且严格遵守 JVM 规范。然而,当 Jackson 将一个 JSON 负载反序列化为一个缺少默认构造函数的对象时,它通常会使用反射(或 Objenesis 这样的库)来为该对象分配内存,*甚至从未真正调用过构造函数*。 你对 Lombok 在编译期生成一个可预测的 `setName()` 方法感到不满,却对一个框架在运行时完全绕过 JVM 对象创建生命周期的做法毫无意见? 再看看 Hibernate 和 JPA。你在实体类中定义了一个标准的 `List`,然后基于它编写业务逻辑。但在运行时,Hibernate 会悄悄拦截你的对象,将你标准的 Java `ArrayList` 替换为它自己的 `PersistentBag` 代理。如果你在数据库会话关闭后再尝试迭代这个列表,你的应用就会抛出那令人头疼的 `LazyInitializationException`。那才是更加不透明的运行时巫术,标准数据结构在隐形的情况下被劫持了。 与之相比,Lombok 简直无聊透顶。生成的代码完全存在于编译期。你可以查看 `.class` 文件。你可以看到它具体生成了什么,如果一个 getter 返回了某个值,你可以直接单步调试。如果我们愿意因为 Jackson 和 Hibernate 提供的开发效率而容忍它们庞大且不透明的运行时机制,那么我们当然也应该对 Lombok 一视同仁地应用同样的务实态度。 AOP 是另一个充满陷阱的工具——我记得有一次在 AWS SWF(Simple Workflow)Flow Framework 实现中调试一个 Try-Catch 块,结果发现 Try 块和 Catch 块竟然是字面意义上在不同的机器上执行的。 ## 未来某个 JDK 版本发布会彻底破坏 Lombok 的编译器 hack 首先,升级恐慌被过度夸大了。除非你迫不及待要在发布第一天就升级,否则 Lombok 通常在主要版本发布后几天内就会修补就绪。(我已经在 JDK 26 上运行 Lombok 了。) 但假设最坏的情况真的发生了,Lombok 被永久禁止在未来的 JDK 中使用。如果那一天真的到来,我的迁移策略极其简单: - 对我的源代码运行 delombok。 - 提交生成的样板代码并移除依赖。 - 在单独的 PR 中升级 JDK。 完全不存在运行时锁定。在那个假想的末日到来之前,我很乐意节省自己的时间,跳过那些我根本不需要写的代码。纯粹在理论上很好,但务实才能交付功能。

相似文章

这段代码是CRAP (2011)

Hacker News Top

作者强调Crap4J是一个有价值的工具,用于在Java项目中识别危险代码且没有误报,赞扬其与Jenkins的集成,并指出它与Java 7不兼容。

当 LLM 反编译器重编译得更多、保留得更少

Lobsters Hottest

论文指出 LLM 反编译器以可重编译性和重执行性作为主要指标时会产生误导:代码虽然能通过所有自带测试,却可能在其他合法输入上行为发散,甚至使已披露的漏洞悄然消失。作者提出 Decompile-Diverge 行为比较预言机,通过合成驱动程序和模糊测试语料检测行为差异,发现部分系统发散率高达 13%。

新DSL如何在LLM时代生存

Hacker News Top

本文探讨了新的领域特定语言(DSL)如何通过强调强大的文档、交互式着陆页以及稳健的工具(如语言服务器和诊断)在LLM时代保持相关性,并以作者的项目Web Pipe和Datafarm为例。

引用 Jeremy Morrell

Simon Willison's Blog

Jeremy Morrell 指出,LLMs 和现代沙箱原语为网络上的可扩展软件创造了机会,使用户能够安全地扩展应用程序并获得增强的能力。