您好,今天的文章解決了一個看似不受歡迎的觀點,我相信它會遇到一些阻力。某件事在技術上可行並不能自動認可其實用性或適用性。因此,我將嘗試證實為什麼我相信使用 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中文網其他相關文章!