这篇博客文章解释了从我参与的项目之一中删除Lombok项目的动机。 它反映了我的个人观点,并不妨碍特定的技术。
大约三年前,我认识了Project Lombok ,这是一个添加Java代码的库。 我从一开始就喜欢它,因为它贡献了很多有用的功能。 我经常处理实体(数据类)和值对象,因此@Data
或Kotlins data class
非常方便也就不足为奇了。 从字面上看,您会得到更多的回报。
我在这里提到Kotlin是因为它共享了我们从Lombok获得的一些属性。
在代码库中采用这样的(语言生成)功能通常会开始缓慢。 代码发展得越多,使用这些功能的组件就越多,因为使用免费获得的功能*方便并且已经习惯了。 使用单个注释或单个关键字,我们选择了可以为我们提供属性访问器的对象,包括equals
/ hashCode
, toString
,生成的构造函数等。
* :实际上, 没有免费的午餐之类的东西 。
现在,有人可以说,只使用您需要的东西,而您完全正确。 如果只需要属性访问器,请使用@Getters
和@Setters
。 如果希望获得equals
/ hashCode
,则添加适当的注释。 真正。 在许多情况下,我们认为我们需要更多的功能,因此为什么当使用单个@Data
注释获得我们想要的(以及更多)时,为什么要将具有多个注释的代码弄乱了。 这不是样板吗? 因此,减少注释的数量似乎是一件好事。
好:不。
原因如下:
偶然的复杂性
通过引入代码生成(这就是Lombok和Kotlin data classes
所做的事情),我们获得了很多功能,但是真正的问题应该是:我是否希望使用该功能? 还是我们想对功能进行明确的控制?
在某些情况下,出于方便考虑,我们使用了数据类。 随着Lombok的删除,我们发现我们隐式使用了免费获得的许多功能* ,例如相等性检查。 随着生成代码的删除,许多测试开始失败,因为这些功能不再可用。 缺少的功能提出了一个问题:是否需要此功能?
只需选择数据类,就可以很容易地忽略这个问题。 与此相反,采用一种明确的方法,我们将花费更多的时间讨论该主题。 可能我们的测试看起来有所不同,或者我们会更明确地了解特定功能。
在没有生成实用程序的情况下显式地控制代码会迫使您考虑该功能是否真正需要。
什么是样板?
样板代码是我们反复需要编写以公开某些功能的代码,而不是告诉代码我们希望此功能开箱即用。 典型的示例是属性访问器(Getters,Setters)和相等性检查( equals
/ hashCode
)。 有时也有构造函数。
与我们以前的想法相反,将Lombok注释分解为自己的组件并不是样板。 它不精确,方便且不负责任。
在编译器周围工作
这是Lombok特定的方面。 Java编译器从未打算用于Lombok要做的事情。 Lombok的维护者为实现Lombok所做的事情做了出色的工作。 这是以针对特定编译器的编译器中的几种解决方法为代价的。 javac
所需的内容在某种程度上与Eclipse的ecj
需要执行的操作不同。
在静态布局中,JDK和Eclipse IDE永不改变,一切都很好。 但是,现实世界是不同的。 Eclipse发布了更新程序,从Java 9开始,Java发布节奏加快了。Lombok项目不是由公司驱动的,而是由时间有限的开源贡献者团队驱动的。
过去,Java升级导致Lombok成为阻止我们升级到较新Java版本的组件:编译器内部发生了变化,Lombok尚无赶超的机会。 随着Lombok用法遍及整个代码库,唯一的选择就是不升级。
但是:从长远来看,不升级不是一种选择。
最终,Lombok赶上了路,为再次升级到新版本开辟了道路。
插入所有东西!
Lombok的一个方面是它需要告诉您的IDE有关生成的类成员的信息。 尽管您的代码中没有例如Setter,但在编译后的代码中却有,所以您的IDE需要知道这一点,以免出现错误。 对于IntelliJ和Netbeans,这不是什么大问题,因为您可以启用注释处理并使用可选的IntelliJ插件。 对于Eclipse,您需要一个可修改Eclipse行为的代理。 如果没有正确的IDE设置,那么任何想要处理代码的人都会收到错误/警告,并提出以下问题:那怎么办?
认知负荷
从某种意义上说,每种非显而易见的行为都会导致复杂性。 同样,每个非默认行为都导致相同的路径。 初次使用这种代码库的人们需要了解如何掌握该代码库。 尽管这不是Lombok特有的,但所有为代码提供附加功能的辅助实用程序(代码生成器,AOP,JVM代理,一般来说字节码操作)都具有被描述为魔术的潜力。 为什么是魔术? 因为在第一刻发生的情况并不明显。 一旦有人向您解释了这个窍门,它就会变得很明显。
其他人更改您的(已编译)代码
使用代码生成功能,我们可以依靠其他人来完成正确的工作。 我们会购买它们,因此他们的工具为我们提供了对我们有用的功能。 我们不再需要为equals
/ hashCode
正确的实现而烦恼,添加属性变得不费吹灰之力,因为这一代人为我们代劳。 手动扩展equals
/ hashCode
并非易事。 某些工具可以为我们做到这一点,但是,正如您可能已经预料到的,我们在不大幅改善我们状况的情况下将tool2
tool1
tool2
。
有时,工具会更改其生成代码的方式或生成的位以及停止生成的位。 找出这些变化并不是一件有趣的事情,但是如果我们已经购买了他们的编程模型,那么我们就别无选择。 唯一的选择是退后,这是以手动实施为代价的。
偶然的复杂性2:构建
根据上下文,这可能仅与我们的项目有关。 我们附带了带有公共API表面的库,并附带了源jar和Javadoc。 默认情况下,Lombok仅适用于您的.class
文件。 这将导致源jar不包含生成的方法,并且Javadoc也不列出生成的成员。 从消除样板代码开始,随着构建复杂性的提高而继续。 为了获得正确的源jar和Javadoc,我们需要向该插件中添加插件,该插件首先要对代码进行delombok,并允许源jar / Javadoc在delomboked源之上运行。
根据您的设置,使用delomboked的源仅用于源jar和Javadoc。 这意味着您将一个版本的代码用于文档目的。 该代码与您用于编译的代码不同。 Lombok本质上导致相同的输出代码。 使这一方面变得明显会使我们感到难受。
复杂性的增加通常会花费更长的构建时间,我们可能会问自己,这是否值得我们得到。
Lombok正在分化社区
即使前面的部分听起来好像我们正在处理严重的问题,但其中许多可能是特定于我们的项目上下文的。 Lombok承诺减少样板代码。 它做得很好。 在面向数据的环境中工作,在该环境中我们需要用于测试的各种对象群甚至在生产代码中,都需要大量的代码才能使用适当的数据对象/值对象。
为hashCode
提供良好的实现并hashCode
。 由于不正确的hashCode
实现,有两个CVE。 忘记在equals
/ hashCode
添加字段是错误的另一个常见来源。
使用代码生成时,我们消除了这些bug来源。 此外,不存在的代码也不会影响我们的测试覆盖率统计信息。 这并不意味着它不需要测试。
查看Lombok删除提交的统计信息,我们看到:
删除:300行
新增:1200线
这很好地说明了使用Lombok带来的好处。 一旦在一个地方使用了Lombok,我们通常会在其他地方继续使用它-因为它已经在类路径中了。 查看已删除的300行,我们应该改为将它们删除为150行,因为它通常是一个import
语句和一个注释,使我们在便捷代码和手动维护代码之间的比例大致为1:8。
我们不需要支付任何代码行,但是拥有更多代码会带来更大的维护面。
看看我的推文 ,有非常相反的意见。 这些反应就是为什么在您应该/不应该使用Project Lombok或Kotlin数据类时没有唯一答案的原因,因为它始终取决于您的团队,上下文和所编写的代码类型。
双重痛苦
不使用代码生成功能会使代码明确。 显式代码始终可以揭示其作用。 显式代码需要设计。 由于立即得到的结果和最初的简单性,进入代码生成功能很诱人。 一旦使用了这些功能,我们将经历不同的情况,并了解尚不明显的方面。 由于相关的成本,很难消除一个非常有益的功能。 还记得1:8的LoC比率吗?
仅仅因为我们想摆脱代码生成,并不意味着我们可以免费删除该工具收到的功能* 。 而是意味着我们需要自行提供此功能。
我这样说:您有一所房子,您将其出租给某些租户,因为租用房屋可以赚钱。 最终,您发现租户很乱,并且开始摆脱租户。 租户出门后,您便会意识到混乱的程度,并开始清理房屋,以免房屋松动。
最终结果是相同的:您为该学习付出了很多努力(可能还有金钱)。
如果您的租户行为正常,则没有理由改变现状。
翻译自: https://www.javacodegeeks.com/2019/07/data-classes-considered-harmful.html