เขียนโค้ด Java แบบ Fat-free ด้วย Project Lombok

เผยแพร่แล้ว: 2022-03-11

มีเครื่องมือและไลบรารีจำนวนมากที่ฉันไม่สามารถจินตนาการได้ว่าตัวเองกำลังเขียนโค้ด Java หากไม่มีวันนี้ ตามเนื้อผ้า สิ่งต่าง ๆ เช่น Google Guava หรือ Joda Time (อย่างน้อยก็ในยุค Java 8 ก่อน) อยู่ท่ามกลางการพึ่งพาที่ฉันลงเอยด้วยการโยนลงในโปรเจ็กต์ของฉันเกือบตลอดเวลา โดยไม่คำนึงถึงโดเมนเฉพาะที่อยู่ในมือ

ลอมบอกสมควรได้รับตำแหน่งใน POM ของฉันหรือ Gradle builds เช่นกันแม้ว่าจะไม่ใช่ยูทิลิตี้ไลบรารี / กรอบงานทั่วไป ลอมบอกมีมาระยะหนึ่งแล้ว (เปิดตัวครั้งแรกในปี 2552) และเติบโตเต็มที่ตั้งแต่นั้นเป็นต้นมา อย่างไรก็ตาม ฉันรู้สึกเสมอว่ามันสมควรได้รับความสนใจมากขึ้น ซึ่งเป็นวิธีที่ยอดเยี่ยมในการจัดการกับการใช้คำฟุ่มเฟือยตามธรรมชาติของ Java

ในโพสต์นี้ เราจะมาสำรวจสิ่งที่ทำให้ลอมบอกเป็นเครื่องมือที่มีประโยชน์

โครงการลอมบอก

Java มีหลายสิ่งหลายอย่างที่นอกเหนือไปจาก JVM ซึ่งเป็นซอฟต์แวร์ที่โดดเด่น Java เติบโตเต็มที่และมีประสิทธิภาพ ชุมชนและระบบนิเวศรอบๆ นั้นมีขนาดใหญ่และมีชีวิตชีวา

อย่างไรก็ตาม ในฐานะที่เป็นภาษาโปรแกรม Java มีความแปลกประหลาดบางอย่างในตัวมันเอง เช่นเดียวกับตัวเลือกการออกแบบที่สามารถทำให้มันค่อนข้างละเอียด เพิ่มโครงสร้างและรูปแบบคลาสที่นักพัฒนา Java มักจำเป็นต้องใช้ และเรามักจะจบลงด้วยโค้ดหลายบรรทัดที่ให้คุณค่าที่แท้จริงเพียงเล็กน้อยหรือไม่มีเลย นอกจากการปฏิบัติตามข้อจำกัดบางชุดหรือแบบแผนของเฟรมเวิร์ก

นี่คือจุดที่ลอมบอกเข้ามาเล่น ช่วยให้เราลดจำนวนโค้ด "boilerplate" ที่เราต้องเขียนได้อย่างมาก ครีเอเตอร์ของลอมบอกเป็นคู่รักที่ฉลาดมาก และมีรสนิยมด้านอารมณ์ขันอย่างแน่นอน คุณไม่ควรพลาดอินโทรนี้ที่เคยทำในการประชุมครั้งก่อน!

มาดูกันว่าลอมบอกใช้เวทมนตร์และตัวอย่างการใช้งานได้อย่างไร

ลอมบอกทำงานอย่างไร

ลอมบอกทำหน้าที่เป็นตัวประมวลผลคำอธิบายประกอบที่ "เพิ่ม" โค้ดให้กับชั้นเรียนของคุณในเวลารวบรวม การประมวลผลคำอธิบายประกอบเป็นคุณลักษณะที่เพิ่มลงในคอมไพเลอร์ Java ในเวอร์ชัน 5 แนวคิดคือผู้ใช้สามารถใส่ตัวประมวลผลคำอธิบายประกอบ (เขียนด้วยตัวเองหรือผ่านการพึ่งพาบุคคลที่สาม เช่น Lombok) ลงใน build classpath จากนั้น เมื่อกระบวนการคอมไพล์ดำเนินไป เมื่อใดก็ตามที่คอมไพเลอร์พบคำอธิบายประกอบ มันก็จะถามว่า: “เฮ้ มีใครใน classpath สนใจ @Annotation นี้ไหม” สำหรับโปรเซสเซอร์เหล่านั้นที่ยกมือขึ้น คอมไพเลอร์จะถ่ายโอนการควบคุมไปยังตัวประมวลผลพร้อมกับคอมไพล์บริบทสำหรับพวกเขา อืม... ประมวลผล

กรณีที่พบบ่อยที่สุดสำหรับตัวประมวลผลคำอธิบายประกอบคือการสร้างไฟล์ต้นทางใหม่หรือดำเนินการตรวจสอบเวลาคอมไพล์บางประเภท

ลอมบอกไม่ได้จัดอยู่ในหมวดหมู่เหล่านี้จริงๆ: สิ่งที่ทำคือการปรับเปลี่ยนโครงสร้างข้อมูลคอมไพเลอร์ที่ใช้เพื่อแสดงโค้ด กล่าวคือ ต้นไม้ไวยากรณ์นามธรรม (AST) การแก้ไข AST ของคอมไพเลอร์ทำให้ลอมบอกเปลี่ยนแปลงการสร้าง bytecode สุดท้ายโดยอ้อม

วิธีการที่ผิดปกติและค่อนข้างเป็นการล่วงล้ำนี้ส่งผลให้ลอมบอกถูกมองว่าเป็นการแฮ็ก แม้ว่าตัวฉันเองจะเห็นด้วยกับคุณลักษณะนี้ในระดับหนึ่ง แทนที่จะมองว่าสิ่งนี้ในความหมายที่ไม่ดีของคำนั้น ฉันจะถือว่าลอมบอกเป็น "ทางเลือกที่ชาญฉลาด มีข้อดีทางเทคนิค และเป็นทางเลือกดั้งเดิม"

ยังมีนักพัฒนาที่มองว่าเป็นแฮ็คและไม่ใช้ลอมบอกด้วยเหตุนี้ นั่นเป็นเรื่องที่เข้าใจได้ แต่จากประสบการณ์ของผม ประโยชน์ของลอมบอกมีประสิทธิผลมากกว่าข้อกังวลใดๆ เหล่านี้ ฉันใช้มันอย่างมีความสุขสำหรับโครงการผลิตมาหลายปีแล้ว

ก่อนที่จะลงรายละเอียด ฉันต้องการสรุปเหตุผลสองประการที่ฉันให้ความสำคัญเป็นพิเศษกับการใช้ลอมบอกในโครงการของฉัน:

  1. ลอมบอกช่วยให้โค้ดของฉันสะอาด กระชับ และตรงประเด็น ฉันพบว่าคลาสที่มีคำอธิบายประกอบในลอมบอกมีความหมายมาก และโดยทั่วไปแล้วฉันพบว่าโค้ดที่มีคำอธิบายประกอบนั้นค่อนข้างเปิดเผยถึงความตั้งใจ แม้ว่าทุกคนบนอินเทอร์เน็ตจะไม่เห็นด้วยก็ตาม
  2. เมื่อฉันเริ่มโครงการและคิดเกี่ยวกับโมเดลโดเมน ฉันมักจะเริ่มต้นด้วยการเขียนชั้นเรียนที่มีงานยุ่งมาก และฉันเปลี่ยนแปลงซ้ำๆ เมื่อฉันคิดเพิ่มเติมและปรับแต่งมัน ในช่วงแรกๆ ลอมบอกช่วยให้ฉันเคลื่อนไหวเร็วขึ้นโดยไม่จำเป็นต้องย้ายไปรอบๆ หรือแปลงรหัสสำเร็จรูปที่สร้างขึ้นสำหรับฉัน

รูปแบบถั่วและวิธีการวัตถุทั่วไป

เครื่องมือและเฟรมเวิร์ก Java จำนวนมากที่เราใช้อาศัยรูปแบบบีน Java Beans เป็นคลาสที่ทำให้ซีเรียลไลซ์ได้ซึ่งมีคอนสตรัคเตอร์ Zero-args ดีฟอลต์ (และอาจเป็นเวอร์ชันอื่น) และเปิดเผยสถานะของพวกเขาผ่าน getters และ setters ซึ่งโดยทั่วไปจะได้รับการสนับสนุนโดยฟิลด์ส่วนตัว เราเขียนสิ่งเหล่านี้จำนวนมาก เช่น เมื่อทำงานกับ JPA หรือเฟรมเวิร์กการทำให้เป็นอนุกรม เช่น JAXB หรือ Jackson

พิจารณา User bean นี้ที่มีแอตทริบิวต์ (คุณสมบัติ) สูงสุดห้ารายการ ซึ่งเราต้องการให้มีตัวสร้างเพิ่มเติมสำหรับแอตทริบิวต์ทั้งหมด การแสดงสตริงที่มีความหมาย และกำหนดความเท่าเทียมกัน/การแฮชในแง่ของช่องอีเมล:

 public class User implements Serializable { private String email; private String firstName; private String lastName; private Instant registrationTs; private boolean payingCustomer; // Empty constructor implementation: ~3 lines. // Utility constructor for all attributes: ~7 lines. // Getters/setters: ~38 lines. // equals() and hashCode() as per email: ~23 lines. // toString() for all attributes: ~3 lines. // Relevant: 5 lines; Boilerplate: 74 lines => 93% meaningless code :( }

เพื่อความกระชับ แทนที่จะรวมการใช้งานจริงของวิธีการทั้งหมด ฉันเพียงแค่แสดงความคิดเห็นโดยระบุวิธีการและจำนวนบรรทัดของโค้ดที่นำไปใช้จริง รหัสต้นแบบนั้นจะมีทั้งหมดมากกว่า 90% ของรหัสสำหรับคลาสนี้!

ยิ่งกว่านั้น ถ้าในภายหลังฉันต้องการพูด เปลี่ยน email เป็น emailAddress หรือให้ registerTs เป็น registrationTs แทนที่จะเป็น Instant ฉันจะต้องอุทิศเวลา (ด้วยความช่วยเหลือจาก IDE ของฉันในบางกรณี เป็น Date ยอมรับ) เพื่อเปลี่ยนแปลงสิ่งต่าง ๆ เช่น รับ /set ชื่อและประเภทของเมธอด แก้ไขตัวสร้างยูทิลิตี้ของฉัน และอื่นๆ อีกครั้งที่เวลาอันมีค่าสำหรับบางสิ่งที่ไม่นำคุณค่าทางธุรกิจมาสู่โค้ดของฉัน

มาดูกันว่าลอมบอกสามารถช่วยได้อย่างไร:

 import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @Getter @Setter @NoArgsConstructor @AllArgsConstructor @ToString @EqualsAndHashCode(of = {"email"}) public class User { private String email; private String firstName; private String lastName; private Instant registrationTs; private boolean payingCustomer; }

โว้ว! ฉันเพิ่งเพิ่มคำอธิบายประกอบ lombok.* และบรรลุสิ่งที่ฉันต้องการ รายการด้านบนนี้เป็นโค้ด ทั้งหมด ที่ฉันต้องเขียนเพื่อสิ่งนี้ ลอมบอกเชื่อมต่อกับกระบวนการคอมไพเลอร์ของฉันและสร้าง ทุกอย่าง ให้ฉัน (ดูภาพหน้าจอด้านล่างของ IDE ของฉัน)

ภาพหน้าจอ IDE

ตามที่คุณสังเกตเห็น ตัวตรวจสอบ NetBeans (และสิ่งนี้จะเกิดขึ้นโดยไม่คำนึงถึง IDE) จะตรวจจับ bytecode ของคลาสที่คอมไพล์แล้ว รวมถึงส่วนเพิ่มเติมของ Lombok ที่นำเข้ามาในกระบวนการ สิ่งที่เกิดขึ้นที่นี่ค่อนข้างตรงไปตรงมา:

  • การใช้ @Getter และ @Setter ฉันสั่งให้ลอมบอกสร้างตัวรับและตัวตั้งค่าสำหรับแอตทริบิวต์ทั้งหมด นี่เป็นเพราะฉันใช้คำอธิบายประกอบในระดับชั้นเรียน ถ้าฉันต้องการเลือกระบุสิ่งที่จะสร้างสำหรับแอตทริบิวต์ใด ฉันอาจใส่คำอธิบายประกอบลงในช่องเอง
  • ขอบคุณ @NoArgsConstructor และ @AllArgsConstructor ฉันได้รับคอนสตรัคเตอร์ว่างเริ่มต้นสำหรับคลาสของฉันรวมถึงอีกอันสำหรับแอตทริบิวต์ทั้งหมด
  • คำอธิบายประกอบ @ToString จะสร้างเมธอด toString() ที่มีประโยชน์โดยอัตโนมัติ โดยแสดงแอตทริบิวต์คลาสทั้งหมดที่นำหน้าด้วยชื่อโดยค่าเริ่มต้น
  • สุดท้าย ในการกำหนดให้คู่ของวิธี equals() และ hashCode() กำหนดไว้ในแง่ของฟิลด์อีเมล ฉันใช้ @EqualsAndHashCode และกำหนดพารามิเตอร์ด้วยรายการฟิลด์ที่เกี่ยวข้อง (เฉพาะอีเมลในกรณีนี้)

การปรับแต่งคำอธิบายประกอบลอมบอก

ลองใช้การปรับแต่งลอมบอกบางอย่างตามตัวอย่างเดียวกันนี้:

  • ฉันต้องการลดการมองเห็นของตัวสร้างเริ่มต้น เนื่องจากฉันต้องการมันด้วยเหตุผลด้านความสอดคล้องของ bean เท่านั้น ฉันจึงคาดหวังให้ผู้บริโภคของคลาสเรียกเฉพาะตัวสร้างที่รับทุกฟิลด์เท่านั้น เพื่อบังคับใช้สิ่งนี้ ฉันกำลังปรับแต่งคอนสตรัคเตอร์ที่สร้างขึ้นด้วย AccessLevel.PACKAGE
  • ฉันต้องการให้แน่ใจว่าฟิลด์ของฉันไม่เคยได้รับค่า null ที่กำหนด ไม่ว่าจะผ่านทางตัวสร้างหรือผ่านเมธอด setter การใส่คำอธิบายประกอบแอตทริบิวต์คลาสด้วย @NonNull ก็เพียงพอแล้ว ลอมบอกจะสร้างการตรวจสอบที่เป็นโมฆะโดยโยน NullPointerException เมื่อเหมาะสมในเมธอดตัวสร้างและตัวตั้งค่า
  • ฉันจะเพิ่มแอตทริบิวต์ password แต่ไม่ต้องการให้แสดงเมื่อโทร toString() ด้วยเหตุผลด้านความปลอดภัย สิ่งนี้ทำได้ผ่านการยกเว้นอาร์กิวเมนต์ของ @ToString
  • ฉันโอเคที่จะเปิดเผยสถานะต่อสาธารณะผ่าน getters แต่ต้องการจำกัดความเปลี่ยนแปลงภายนอก ดังนั้นฉันจึงออกจาก @Getter เหมือนเดิม แต่ใช้ AccessLevel.PROTECTED อีกครั้งสำหรับ @Setter
  • บางทีฉันอาจต้องการบังคับข้อจำกัดบางอย่างในฟิลด์ email เพื่อที่ว่าหากได้รับการแก้ไข จะมีการเรียกใช้การตรวจสอบบางประเภท สำหรับสิ่งนี้ ฉันแค่ใช้วิธี setEmail() ด้วยตัวเอง ลอมบอกจะละเว้นการสร้างสำหรับวิธีการที่มีอยู่แล้ว

นี่คือลักษณะที่คลาส User จะมีลักษณะ:

 import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.Setter; import lombok.ToString; @Getter @Setter(AccessLevel.PROTECTED) @NoArgsConstructor(access = AccessLevel.PACKAGE) @AllArgsConstructor @ToString(exclude = {"password"}) @EqualsAndHashCode(of = {"email"}) public class User { private @NonNull String email; private @NonNull byte[] password; private @NonNull String firstName; private @NonNull String lastName; private @NonNull Instant registrationTs; private boolean payingCustomer; protected void setEmail(String email) { // Check for null (=> NullPointerException) // and valid email code (=> IllegalArgumentException) this.email = email; } }

โปรดทราบว่าสำหรับคำอธิบายประกอบบางรายการ เรากำลังระบุแอตทริบิวต์ของคลาสเป็นสตริงธรรมดา ไม่ใช่ปัญหา เพราะลอมบอกจะโยนข้อผิดพลาดในการคอมไพล์หากเรา เช่น พิมพ์ผิดหรืออ้างถึงฟิลด์ที่ไม่มีอยู่ ด้วยลอมบอก เราปลอดภัย

นอกจากนี้ เช่นเดียวกับ setEmail() ลอมบอกก็ใช้ได้ และไม่สร้างสิ่งใดสำหรับวิธีการที่โปรแกรมเมอร์ได้นำไปใช้แล้ว สิ่งนี้ใช้กับวิธีการและตัวสร้างทั้งหมด

โครงสร้างข้อมูลที่ไม่เปลี่ยนรูป

กรณีการใช้งานอื่นที่ Lombok มีความยอดเยี่ยมคือเมื่อสร้างโครงสร้างข้อมูลที่ไม่เปลี่ยนรูปแบบ เหล่านี้มักจะเรียกว่า "ประเภทค่า" บางภาษามีการสนับสนุนในตัวสำหรับสิ่งเหล่านี้ และยังมีข้อเสนอสำหรับการรวมสิ่งนี้เข้ากับเวอร์ชัน Java ในอนาคต

สมมติว่าเราต้องการสร้างแบบจำลองการตอบสนองต่อการดำเนินการเข้าสู่ระบบของผู้ใช้ นี่คือประเภทของอ็อบเจ็กต์ที่เราต้องการที่จะสร้างอินสแตนซ์และกลับไปยังเลเยอร์อื่นๆ ของแอปพลิเคชัน (เช่น เพื่อให้เป็น JSON ต่อเนื่องเป็นเนื้อหาของการตอบสนอง HTTP) LoginResponse ดังกล่าวไม่จำเป็นต้องเปลี่ยนแปลงเลย และลอมบอกสามารถช่วยอธิบายได้อย่างกระชับ แน่นอนว่ายังมีกรณีการใช้งานอื่นๆ อีกมากสำหรับโครงสร้างข้อมูลที่ไม่เปลี่ยนรูปแบบ (เป็นแบบมัลติเธรดและเป็นมิตรกับแคช ท่ามกลางคุณสมบัติอื่นๆ) แต่มาดูตัวอย่างง่ายๆ นี้กัน:

 import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.ToString; import lombok.experimental.Wither; @Getter @RequiredArgsConstructor @ToString @EqualsAndHashCode public final class LoginResponse { private final long userId; private final @NonNull String authToken; private final @NonNull Instant loginTs; @Wither private final @NonNull Instant tokenExpiryTs; }

น่าสังเกตที่นี่:

  • มีการแนะนำคำอธิบายประกอบ @RequiredArgsConstructor ตั้งชื่ออย่างเหมาะเจาะ สิ่งที่มันทำคือสร้างคอนสตรัคเตอร์สำหรับฟิลด์สุดท้ายทั้งหมดที่ยังไม่ได้เริ่มต้น
  • ในกรณีที่เราต้องการนำ LoginResonse ที่ออกก่อนหน้านี้มาใช้ใหม่ (ลองนึกภาพ เช่น การดำเนินการ "รีเฟรชโทเค็น") เราไม่ต้องการแก้ไขอินสแตนซ์ที่มีอยู่ของเราอย่างแน่นอน แต่เราต้องการสร้างอินสแตนซ์ใหม่ตามนั้น . ดูว่าคำอธิบายประกอบ @Wither ช่วยเราได้อย่างไร: มันบอกให้ลอมบอกสร้าง withTokenExpiryTs(Instant tokenExpiryTs) ที่สร้างอินสแตนซ์ใหม่ของ LoginResponse โดยมีค่าอินสแตนซ์ with'ed ทั้งหมด ยกเว้นค่าใหม่ที่เรากำลังระบุ คุณต้องการพฤติกรรมนี้สำหรับทุกฟิลด์หรือไม่? เพียงเพิ่ม @Wither ในการประกาศคลาสแทน

@Data และ @Value

กรณีการใช้งานทั้งสองที่กล่าวถึงจนถึงตอนนี้เป็นเรื่องธรรมดามากที่ลอมบอกจัดส่งคำอธิบายประกอบสองสามรายการเพื่อให้สั้นลง: การทำคำอธิบายประกอบคลาสด้วย @Data จะทำให้ลอมบอกทำงานเหมือนกับว่าได้รับการใส่คำอธิบายประกอบด้วย @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor ในทำนองเดียวกัน การใช้ @Value จะทำให้ชั้นเรียนของคุณเป็นแบบที่ไม่เปลี่ยนรูปแบบ (และเป็นขั้นสุดท้าย) อีกครั้ง ราวกับว่ามีคำอธิบายประกอบในรายการด้านบน

รูปแบบตัวสร้าง

กลับไปที่ตัวอย่าง User ของเรา หากเราต้องการสร้างอินสแตนซ์ใหม่ เราจำเป็นต้องใช้ Constructor ที่มีอาร์กิวเมนต์ไม่เกิน 6 อาร์กิวเมนต์ นี่เป็นจำนวนที่ค่อนข้างมากแล้ว ซึ่งจะยิ่งแย่ลงไปอีกหากเราเพิ่มแอตทริบิวต์ในชั้นเรียนเพิ่มเติม สมมติว่าเราต้องการตั้งค่าเริ่มต้นบางอย่างสำหรับฟิลด์ lastName และ payingCustomer เงิน

ลอมบอกใช้คุณลักษณะ @Builder ที่มีประสิทธิภาพมาก ทำให้เราสามารถใช้รูปแบบตัวสร้างเพื่อสร้างอินสแตนซ์ใหม่ได้ มาเพิ่มในคลาสผู้ใช้ของเรา:

 import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.Setter; import lombok.ToString; @Getter @Setter(AccessLevel.PROTECTED) @NoArgsConstructor(access = AccessLevel.PACKAGE) @AllArgsConstructor @ToString(exclude = {"password"}) @EqualsAndHashCode(of = {"email"}) @Builder public class User { private @NonNull String email; private @NonNull byte[] password; private @NonNull String firstName; private @NonNull String lastName = ""; private @NonNull Instant registrationTs; private boolean payingCustomer = false; }

ตอนนี้เราสามารถสร้างผู้ใช้ใหม่ได้อย่างคล่องแคล่วดังนี้:

 User user = User .builder() .email("[email protected]") .password("secret".getBytes(StandardCharsets.UTF_8)) .firstName("Miguel") .registrationTs(Instant.now()) .build();

เป็นเรื่องง่ายที่จะจินตนาการว่าโครงสร้างนี้สะดวกเพียงใดเมื่อชั้นเรียนของเราเติบโตขึ้น

การมอบหมาย/องค์ประกอบ

หากคุณต้องการทำตามกฎที่มีเหตุผลของ "องค์ประกอบที่โปรดปรานมากกว่าการสืบทอด" นั่นเป็นสิ่งที่ Java ไม่ได้ช่วยจริงๆ ใช้คำฟุ่มเฟือย ถ้าคุณต้องการเขียนออบเจ็กต์ คุณมักจะต้องเขียนการเรียกเมธอดการมอบหมายทั้งหมด

ลอมบอกเสนอวิธีแก้ปัญหานี้ผ่าน @Delegate ลองมาดูตัวอย่างกัน

ลองนึกภาพว่าเราต้องการแนะนำแนวคิดใหม่ของ ContactInformation นี่คือข้อมูลบางส่วนที่ User ของเรามี และเราอาจต้องการให้ชั้นเรียนอื่นๆ มีด้วยเช่นกัน จากนั้นเราสามารถจำลองสิ่งนี้ผ่านอินเทอร์เฟซดังนี้:

 public interface HasContactInformation { String getEmail(); String getFirstName(); String getLastName(); }

จากนั้นเราจะแนะนำคลาส ContactInformation ใหม่โดยใช้ลอมบอก:

 import lombok.Data; @Data public class ContactInformation implements HasContactInformation { private String email; private String firstName; private String lastName; }

และสุดท้าย เราสามารถ refactor User เพื่อเขียนด้วย ContactInformation และใช้ Lombok เพื่อสร้างการเรียกผู้รับมอบสิทธิ์ที่จำเป็นทั้งหมดเพื่อให้ตรงกับสัญญาอินเทอร์เฟซ:

 import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.Setter; import lombok.ToString; import lombok.experimental.Delegate; @Getter @Setter(AccessLevel.PROTECTED) @NoArgsConstructor(access = AccessLevel.PACKAGE) @AllArgsConstructor @ToString(exclude = {"password"}) @EqualsAndHashCode(of = {"contactInformation"}) public class User implements HasContactInformation { @Getter(AccessLevel.NONE) @Delegate(types = {HasContactInformation.class}) private final ContactInformation contactInformation = new ContactInformation(); private @NonNull byte[] password; private @NonNull Instant registrationTs; private boolean payingCustomer = false; }

โปรดทราบว่าฉันไม่จำเป็นต้องเขียนการใช้งานสำหรับวิธีการของ HasContactInformation : นี่คือสิ่งที่เรากำลังบอกให้ลอมบอกทำ โดยมอบหมายการโทรไปยังอินสแตนซ์ ContactInformation ของเรา

นอกจากนี้ เนื่องจากฉันไม่ต้องการให้เข้าถึงอินสแตนซ์ที่ได้รับมอบสิทธิ์จากภายนอก ฉันกำลังปรับแต่งด้วย @Getter(AccessLevel.NONE) เพื่อป้องกันการสร้าง getter อย่างมีประสิทธิภาพ

ตรวจสอบข้อยกเว้น

ดังที่เราทราบกันดี Java แยกความแตกต่างระหว่างข้อยกเว้นที่ตรวจสอบแล้วและไม่ได้ตรวจสอบ นี่เป็นแหล่งดั้งเดิมสำหรับการโต้เถียงและวิพากษ์วิจารณ์ภาษา เนื่องจากการจัดการข้อยกเว้นบางครั้งอาจส่งผลมากเกินไป โดยเฉพาะอย่างยิ่งเมื่อจัดการกับ API ที่ออกแบบมาเพื่อโยนข้อยกเว้นที่ตรวจสอบแล้ว ดังนั้นจึงบังคับให้นักพัฒนาซอฟต์แวร์จับพวกเขาหรือประกาศวิธีการของเรา โยนพวกเขา

พิจารณาตัวอย่างนี้:

 public class UserService { public URL buildUsersApiUrl() { try { return new URL("https://apiserver.com/users"); } catch (MalformedURLException ex) { // Malformed? Really? throw new RuntimeException(ex); } } }

นี่เป็นรูปแบบทั่วไป: เราทราบดีว่า URL ของเรามีรูปแบบที่ดี แต่เนื่องจากตัวสร้าง URL ส่งข้อยกเว้นที่ตรวจสอบแล้ว เราจึงถูกบังคับให้จับหรือประกาศวิธีการของเราที่จะโยนมันทิ้งและนำผู้โทรออกในสถานการณ์เดียวกัน การรวมข้อยกเว้นที่ตรวจสอบเหล่านี้ไว้ใน RuntimeException เป็นแนวทางปฏิบัติที่ขยายออกไปมาก และสิ่งนี้จะยิ่งแย่ลงไปอีกหากจำนวนของข้อยกเว้นที่ตรวจสอบที่เราต้องจัดการเพิ่มขึ้นเมื่อเราเขียนโค้ด

ดังนั้นนี่คือสิ่งที่ @SneakyThrows ของลอมบอกมีไว้สำหรับ มันจะรวมข้อยกเว้นที่ตรวจสอบแล้วซึ่งจะถูกโยนลงในวิธีการของเราให้เป็นแบบที่ไม่ถูกตรวจสอบและทำให้เราพ้นจากความยุ่งยาก:

 import lombok.SneakyThrows; public class UserService { @SneakyThrows public URL buildUsersApiUrl() { return new URL("https://apiserver.com/users"); } }

การบันทึก

คุณเพิ่มอินสแตนซ์ตัวบันทึกในชั้นเรียนเช่นนี้บ่อยเพียงใด (ตัวอย่าง SLF4J)

 private static final Logger LOG = LoggerFactory.getLogger(UserService.class);

ฉันจะเดาค่อนข้างมาก เมื่อทราบสิ่งนี้ ผู้สร้าง Lombok ได้ใช้คำอธิบายประกอบที่สร้างอินสแตนซ์ตัวตัดไม้ด้วยชื่อที่ปรับแต่งได้ (ค่าเริ่มต้นคือบันทึก) ซึ่งสนับสนุนเฟรมเวิร์กการบันทึกที่ใช้บ่อยที่สุดบนแพลตฟอร์ม Java เช่นนี้ (อีกครั้งตาม SLF4J):

 import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @Slf4j public class UserService { @SneakyThrows public URL buildUsersApiUrl() { log.debug("Building users API URL"); return new URL("https://apiserver.com/users"); } }

คำอธิบายประกอบรหัสที่สร้าง

หากเราใช้ลอมบอกเพื่อสร้างโค้ด ดูเหมือนว่าเราจะสูญเสียความสามารถในการใส่คำอธิบายประกอบวิธีการเหล่านั้นเนื่องจากเราไม่ได้เขียนโค้ดเหล่านั้นจริงๆ แต่นี่ไม่เป็นความจริงเลย ในทางกลับกัน ลอมบอกช่วยให้เราสามารถบอกได้ว่าเราต้องการสร้างคำอธิบายประกอบโค้ดอย่างไร โดยใช้สัญกรณ์ที่ค่อนข้างแปลก

ลองพิจารณาตัวอย่างนี้ ที่กำหนดเป้าหมายการใช้เฟรมเวิร์กการฉีดการพึ่งพา: เรามีคลาส UserService ที่ใช้การแทรกคอนสตรัคเตอร์เพื่อรับการอ้างอิงไปยัง UserRepository และ UserApiClient

 package com.mgl.toptal.lombok; import javax.inject.Inject; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor(onConstructor = @__(@Inject)) public class UserService { private final UserRepository userRepository; private final UserApiClient userApiClient; // Instead of: // // @Inject // public UserService(UserRepository userRepository, // UserApiClient userApiClient) { // this.userRepository = userRepository; // this.userApiClient = userApiClient; // } }

ตัวอย่างด้านบนแสดงวิธีการใส่คำอธิบายประกอบให้กับ Constructor ที่สร้างขึ้น ลอมบอกช่วยให้เราทำสิ่งเดียวกันสำหรับเมธอดและพารามิเตอร์ที่สร้างขึ้นได้เช่นกัน

เรียนรู้เพิ่มเติม

การใช้งานลอมบอกที่อธิบายในโพสต์นี้เน้นที่คุณสมบัติเหล่านั้นที่ฉันพบว่ามีประโยชน์มากที่สุดในช่วงหลายปีที่ผ่านมา อย่างไรก็ตาม ยังมีฟีเจอร์และการปรับแต่งอื่นๆ อีกมากมายที่พร้อมใช้งาน

เอกสารของลอมบอกให้ข้อมูลและละเอียดถี่ถ้วน พวกเขามีหน้าเฉพาะสำหรับทุกคุณลักษณะ (คำอธิบายประกอบ) พร้อมคำอธิบายและตัวอย่างที่ละเอียดมาก หากคุณพบว่าโพสต์นี้น่าสนใจ เราขอแนะนำให้คุณเจาะลึกลงไปในลอมบอกและเอกสารประกอบเพื่อหาข้อมูลเพิ่มเติม

ไซต์โครงการจัดทำเอกสารวิธีใช้ลอมบอกในสภาพแวดล้อมการเขียนโปรแกรมต่างๆ กล่าวโดยย่อ รองรับ IDE ที่ได้รับความนิยมมากที่สุด (Eclipse, NetBeans และ IntelliJ) ตัวฉันเองเปลี่ยนจากที่หนึ่งไปอีกที่หนึ่งเป็นประจำในแต่ละโครงการและใช้ลอมบอกกับพวกเขาทั้งหมดอย่างไม่มีที่ติ

เดลมบอก!

Delombok เป็นส่วนหนึ่งของ “Lombok toolchain” และมีประโยชน์มาก โดยทั่วไปแล้วสิ่งที่ทำคือสร้าง ซอร์สโค้ด Java สำหรับโค้ดที่มี หมายเหตุประกอบในลอมบอก โดยดำเนินการแบบเดียวกับที่ bytecode สร้างขึ้นในลอมบอก

นี่เป็นตัวเลือกที่ยอดเยี่ยมสำหรับผู้ที่กำลังพิจารณานำลอมบอกมาใช้ แต่ยังไม่ค่อยแน่ใจ คุณสามารถเริ่มใช้งานได้อย่างอิสระและจะไม่มี "การล็อคอินจากผู้ขาย" ในกรณีที่คุณหรือทีมของคุณเสียใจกับการเลือกในภายหลัง คุณสามารถใช้ delombok เพื่อสร้างซอร์สโค้ดที่เกี่ยวข้อง ซึ่งคุณสามารถใช้งานได้โดยไม่ต้องพึ่งพา Lombok ที่เหลืออยู่

Delombok เป็นเครื่องมือที่ยอดเยี่ยมในการเรียนรู้ว่าลอมบอกจะทำอะไร มีวิธีที่ง่ายมากในการเชื่อมต่อกับกระบวนการสร้างของคุณ

ทางเลือก

มีเครื่องมือมากมายในโลกของ Java ที่ใช้ตัวประมวลผลคำอธิบายประกอบที่คล้ายคลึงกันเพื่อปรับปรุงหรือแก้ไขโค้ดของคุณในขณะรวบรวม เช่น Immutables หรือ Google Auto Value สิ่งเหล่านี้ (และอื่น ๆ แน่นอน!) ทับซ้อนกับคุณสมบัติของลอมบอก ฉันชอบแนวทางของ Immutables มากเป็นพิเศษและเคยใช้ในบางโครงการด้วย

นอกจากนี้ ยังควรสังเกตด้วยว่ามีเครื่องมือที่ยอดเยี่ยมอื่นๆ ที่มีคุณสมบัติคล้ายคลึงกันสำหรับ "การเพิ่มประสิทธิภาพโค้ดไบต์" เช่น Byte Buddy หรือ Javassist โดยทั่วไปแล้วสิ่งเหล่านี้จะทำงานที่รันไทม์และประกอบด้วยโลกของตัวเองที่อยู่นอกเหนือขอบเขตของโพสต์นี้

ชวากระชับ

มีภาษาเป้าหมาย JVM ที่ทันสมัยจำนวนหนึ่งที่ให้แนวทางการออกแบบที่มีสำนวนมากขึ้น หรือแม้แต่ระดับภาษา เพื่อช่วยแก้ไขปัญหาเดียวกัน Groovy, Scala และ Kotlin เป็นตัวอย่างที่ดีอย่างแน่นอน แต่ถ้าคุณกำลังทำงานในโครงการ Java เท่านั้น ลอมบอกเป็นเครื่องมือที่ดีที่จะช่วยให้โปรแกรมของคุณกระชับ แสดงออก และสามารถบำรุงรักษาได้มากขึ้น

ที่เกี่ยวข้อง: The Dart Language: เมื่อ Java และ C # ไม่คมชัดเพียงพอ