您好,今天的文章解决了一个看似不受欢迎的观点,我相信它会遇到一些阻力。仅仅因为某件事在技术上可行并不能自动认可其实用性或适用性。因此,我将尝试证实为什么我相信使用 Lombok 可能会对您的代码产生不利影响。
在深入研究不太流行的细节之前,让我简要解释一下 Lombok 库的功能。
Project Lombok 充当一个库,在编译时将代码注入到类中,这看起来几乎很神奇。要理解它的操作,了解Java编译过程是必不可少的。 Java编译主要分为三个阶段(图1):解析与输入、注解处理、分析与生成,如下图所示:
图 1 – 抽象语法树 (AST)
解析并输入:
在这里,编译器将源文件转换为抽象语法树(AST)。仅因语法无效而引发错误,而不会因错误的类或方法使用而引发错误。
注释处理:
在此阶段,自定义注释处理器会验证类或生成新资源(例如源文件)。如果生成新的源,这可能会触发新的编译周期。
分析并生成:
在最后阶段,编译器从 AST 生成字节码,检查损坏的引用,验证逻辑流,执行类型擦除,并对语法糖进行脱糖。
Project Lombok 作为注释处理器运行,通过注入新方法、字段或表达式来修改 AST。与生成新源的典型处理器不同,Lombok 会更改现有类,这一区别使其能够直接影响生成的字节码。
J2SE 1.5 中引入的 AnnotationProcessor 无法更改现有文件。它只能创建新文件或字节码。这使得 lombok 的实现很有趣,因为它们在编译阶段使用 AnnotationProcessor 来修改现有的 java 类文件。这是 的概述
使用 Lombok 的编译过程(
图 2)。
图2 – 编译过程和Lombok
针对龙目岛的案件增加编译时间
利益错位
@Data @Builder public class Course { public enum Type { ONLINE, ONSITE; @JsonValue @Override public String toString() { return super.toString().toLowerCase(); } } private long id; private Type type; }
public class CourseCreator { public static Course createCourse(Enrollment enrollment, Registration registration) { Course.Type courseType = enrollment.getVenue().equals(registration.getVenue()) ? Course.Type.ONSITE : Course.Type.ONLINE; return Course.builder() .id(enrollment.getId()) .type(courseType) .build(); } public static void main(String[] args) { Registration registration = new Registration(); Enrollment enrollment = new Enrollment(); Course course = createCourse(enrollment, registration); System.out.println(course); } }
如果我们在构建器中省略 .type(),我们将实例化什么类型的课程?
这行代码可以编译,但它让我们产生疑问:我们实际上创建了什么类型的课程?这是有效的课程实例吗?
Course.builder().id(1L).build();
考虑调整实施,以确保任何课程创建都是明确的并受到业务环境的约束:
@Data public class Course { private enum Type { ONLINE, ONSITE; @JsonValue @Override public String toString() { return super.toString().toLowerCase(); } } public static Course online(long id) { return new Course(id, Type.ONLINE); } public static Course onsite(long id) { return new Course(id, Type.ONSITE); } private long id; private Type type; public boolean isOnline() { return Type.ONLINE.equals(this.type); } public boolean isOnsite() { return Type.ONSITE.equals(this.type); } }
public class CourseManagement { public static Course createAppropriateCourse(Enrollment enrollment, Registration registration) { return enrollment.getVenue().equals(registration.getVenue()) ? Course.onsite(enrollment.getId()) : Course.online(enrollment.getId()); } public static void main(String[] args) { Registration registration = new Registration(); Enrollment enrollment = new Enrollment(); Course createdCourse = createAppropriateCourse(enrollment, registration); System.out.println(createdCourse); } }
此外,通过将 Type 枚举设为私有并提供清晰、显式的方法(如 isOnline() 和 isOnsite()),我们确保仅公开和操作有效状态,从而保护域完整性。
Through this thoughtful restructuring, we demonstrate that while tools like Lombok can significantly reduce boilerplate, they are not substitutes for careful design and a deep understanding of the domain. It underscores that Lombok should be employed judiciously, complementing rather than overshadowing robust architectural practices. This ensures that the elegance of our code does not come at the expense of its correctness and clarity.
The argument that getters and setters reduce boilerplate falls short when Java offers alternatives like the Record classes from Java 14.
@Data public class Movie { private String title; private int releaseYear; } // Can be replaced with: public record Movie(String title, int releaseYear) {}
Having null in your code - aside from inputs is generally considered problematic and is often indicative of deeper design issues. The prevalent advice is to avoid returning null whenever possible. Instead, opt for alternatives such as returning non-null collections, utilizing null objects, or throwing exceptions to signify unusual or exceptional conditions. This strategic avoidance means null checks become redundant in most parts of your code.
To distance from Lombok's @NonNull annotation and ensure robustness in Java natively, the Objects.requireNonNull() method from the java.util.Objects class is incredibly useful.
This method streamlines null checking by ensuring that an object is not null, and it throws a NullPointerException with a clear message if it is. This explicit exception-throwing mechanism prevents latent null-related bugs from surfacing in runtime, promoting earlier detection during the development cycle. Here’s an example showing how this method can replace Lombok's functionality
Using Lombok's@NonNull:
public class NonNullExample { private Student student; public NonNullExample(@NonNull Student student) { this.student = student; } }
Equivalent pure Java approach:
import java.util.Objects; public class NonNullExample { private Student student; public NonNullExample(Student student) { this.student = Objects.requireNonNull(student, "Student cannot be null"); } }
This transition to native Java handling enhances code transparency by making the null-check explicit, which is advantageous for code maintenance and understanding.
Constructors play a critical role in how classes interact within your software architecture. A well-designed class should have a variety of constructors that accommodate different use cases, promoting reusability and flexibility. If your constructors merely replicate field assignments, the underlying issue isn't the need to write boilerplate code; rather, it's the risk of fostering a non-reusable and inflexible design that Lombok cannot rectify. Proper constructor design allows a class to be integrated and utilized in a multitude of scenarios, enhancing the overall robustness and adaptability of your codebase.
Lombok's popularity predominantly stems from its ability to reduce boilerplate code, particularly in domain-specific classes like transfer and data objects. While Lombok effectively diminishes the visible clutter by auto-generating necessary code like getters, setters, equals, hashCode, and toString methods, this convenience might obscure potential pitfalls. However, with the advent of Java Records introduced in Java 14, there is a preferable alternative that natively supports the concise declaration of immutable data carriers. Most integrated
development environments (IDEs) are also equipped to automatically generate these boilerplate codes with minimal user input, offering a balance between Lombok’s automation and the control of traditional Java coding.
Project Lombok's dependency on the underlying Java version poses a significant compatibility risk. As Java evolves, the Abstract Syntax Tree (AST) structure and its interpretation could change, necessitating continuous updates to Lombok to ensure compatibility. This creates a fragile dependency where upgrading to a newer Java version could potentially break your build if Lombok is not simultaneously updated to support these changes. The reliance on unofficial or private APIs to modify class definitions further exacerbates this issue because these APIs could be restricted or altered in future Java releases, threatening Lombok’s long-term viability.
使用仅使用标准 Java 编译器选项的工具构建项目时,使用 Lombok 可能会导致复杂化。例如,如果您的代码使用 Lombok 生成的 getter 和 setter,则直接使用 javac 进行编译而不进行 Lombok 预处理可能会导致错误,指示缺少方法。虽然有些人可能认为 Lombok 注入代码的能力是一个聪明的“技巧”,但批判性地评估相关风险和替代方案至关重要。问题的核心在于Java的注解处理规范并不正式支持在编译期间修改现有的类。依赖这些非官方技术使得 Lombok 容易受到未来 Java 更新的影响,这些更新可能会破坏或禁用其功能。
最终,这些考虑因素强调了不仅要评估 Lombok 等工具的直接好处的重要性,还要评估它们对可维护性、兼容性以及与 Java 标准的一致性的长期影响。随着 Java 的不断发展,对稳定、标准化功能的依赖对于确保软件项目的可持续性和可靠性变得越来越重要。
Lombok 似乎是 Java 开发的便捷捷径,但它将 Java 代码转换为特定于域的版本,我喜欢称之为 “Lombok Java”。必须认识到,过度依赖 Lombok 可能会掩盖 Java 本质,可能会导致代码不够健壮,并且在没有 Lombok 的情况下更难以管理。
如果过度依赖 Lombok 是管理代码库的解决方案,那么可能是时候重新评估底层架构和实践了。 Java 的真正优势在于其清晰性和结构,而不是外部库提供的快捷方式。
如果有机会,我会选择从我的项目中放弃 Lombok。
以上是为什么我认为 Lombok 应该从 Java 项目中丢弃的详细内容。更多信息请关注PHP中文网其他相关文章!