يعود لومبوك إلى عظمة جافا



نحن في Grubhub نستخدم Java في الخلفية بأكملها تقريبًا. هذه لغة مثبتة أثبتت سرعتها وموثوقيتها على مدار العشرين عامًا الماضية. ولكن على مر السنين ، ما زال عصر "الرجل العجوز" يؤثر.

تعد Java واحدة من أكثر لغات JVM شيوعًا ، ولكنها ليست واحدة فقط. في السنوات الأخيرة ، كان ينافس Scala و Clojure و Kotlin ، الذين يوفرون وظائف جديدة وميزات لغة محسنة. باختصار ، يسمحون لك بعمل المزيد من خلال رمز أكثر إيجازًا.

هذه الابتكارات في النظام البيئي JVM مثيرة للاهتمام للغاية. بسبب المنافسة ، فإن Java مجبرة على التغيير من أجل الحفاظ على قدرتها التنافسية. إن جدول الإصدار الجديد لمدة ستة أشهر والعديد من JEP (مقترحات تحسين JDK) في Java 8 (Valhalla ، Inferable Type Inference، Loom) دليل على أن Java ستبقى لغة تنافسية لسنوات.

ومع ذلك ، فإن حجم وحجم Java يعني أن التطوير يسير ببطء أكثر مما نود ، ناهيك عن الرغبة القوية في الحفاظ على التوافق مع الإصدارات بأي ثمن. في أي تطور ، يجب أن تكون الأولوية الأولى هي الوظائف ، ولكن هنا تم تطوير الوظائف الضرورية لفترة طويلة جدًا ، إن وجدت ، باللغة. لهذا السبب ، فإننا في Grubhub نستخدم Project Lombok لتحسين جافا وتحسينه تحت تصرفنا الآن. يعد مشروع Lombok أحد مكونات برنامج التحويل البرمجي الذي يضيف "كلمات رئيسية" جديدة إلى Java ويحول التعليقات التوضيحية إلى تعليمات Java البرمجية ، مما يقلل من جهود التطوير ويوفر بعض الوظائف الإضافية.

تكوين لومبوك


تسعى Grubhub دائمًا إلى تحسين دورة حياة البرنامج ، ولكن كل أداة وعملية جديدة لها تكلفة في الاعتبار. لحسن الحظ ، لتوصيل Lombok ، ما عليك سوى إضافة بضعة أسطر إلى ملف gradle.

يقوم Lombok بتحويل التعليقات التوضيحية في التعليمات البرمجية المصدر إلى عبارات Java قبل قيام المترجم بمعالجتها: لا تعتمد التبعية lombok في وقت التشغيل ، لذا لن يؤدي استخدام البرنامج المساعد إلى زيادة حجم التجميع. لتكوين Lombok باستخدام Gradle (يعمل أيضًا مع Maven) ، ما عليك سوى إضافة الأسطر التالية إلى ملف build.gradle :

 plugins { id 'io.franzbecker.gradle-lombok' version '1.14' id 'java' } repositories { jcenter() // or Maven central, required for Lombok dependency } lombok { version = '1.18.4' sha256 = "" } 

مع Lombok ، لن يكون كود المصدر الخاص بنا رمز Java صالحًا. لذلك ، ستحتاج إلى تثبيت مكون إضافي لـ IDE ، وإلا فلن تفهم بيئة التطوير ما تتعامل معه. يدعم لومبوك جميع معرفات جافا الرئيسية. التكامل السلس. تستمر جميع وظائف مثل "show show" و "go to implementation" في العمل كما كان من قبل ، حيث تنقلك إلى الحقل / الفصل المقابل.

لومبوك في العمل


أفضل طريقة للتعرف على لومبوك هي رؤيتها تعمل. النظر في بعض الأمثلة النموذجية.

تسترجع كائن POJO


باستخدام "كائنات Java القديمة الجيدة" (POJOs) ، نفصل البيانات عن المعالجة لتسهيل قراءة التعليمات البرمجية ونقلها عبر الشبكة. تحتوي لعبة POJO البسيطة على العديد من الحقول الخاصة ، بالإضافة إلى المناظير والمستويات المقابلة لها. إنهم يقومون بهذه المهمة ، لكنهم يحتاجون إلى الكثير من التعليمات البرمجية.

يساعد لومبوك على استخدام POJO بطريقة أكثر مرونة ومنظومة بدون رمز إضافي. هذه هي @Data يمكننا بها تبسيط POJO الأساسي مع التعليق التوضيحي @Data :

 @Data public class User { private UUID userId; private String email; } 

@Data مجرد تعليق توضيحي مناسب يطبق تعليقات توضيحية متعددة لـ Lombok مرة واحدة.

  • ينشئ @ToString toString() ، والذي يتكون من تمثيل أنيق للكائن: اسم الفئة ، وجميع الحقول وقيمها.
  • @EqualsAndHashCode بإنشاء تطبيقات equals و hashCode ، والتي تستخدم بشكل افتراضي الحقول غير الثابتة وغير الثابتة ، ولكنها قابلة للتخصيص.
  • @Getter / @Setter يولِّد @Getter / @Setter للحقول الخاصة.
  • يقوم @RequiredArgsConstructor بإنشاء مُنشئ باستخدام الوسائط المطلوبة ، حيث تكون الحقول والحقول النهائية مع التعليقات التوضيحية @NonNull (المزيد حول هذا أدناه).

يغطي هذا التعليق التوضيحي بمفرده وبأناقة العديد من حالات الاستخدام النموذجية. لكن POJO لا يغطي دائمًا الوظائف اللازمة. @Data عبارة عن فئة قابلة للتعديل بالكامل ، والتي يمكن أن تؤدي إساءة استخدامها إلى زيادة التعقيد وتقييد التزامن ، مما يؤثر سلبًا على بقاء التطبيق.

هناك حل آخر. دعنا نعود إلى فئة User لدينا ، وجعلها غير قابلة للتغيير ، وإضافة بعض التعليقات التوضيحية المفيدة الأخرى.

 @Value @Builder(toBuilder = true) public class User { @NonNull UUID userId; @NonNull String email; @Singular Set<String> favoriteFoods; @NonNull @Builder.Default String avatar = “default.png”; } 

@Value التعليق التوضيحي @Data فيما عدا أن جميع الحقول خاصة ونهائية بشكل افتراضي ، ولا يتم إنشاء @Data . بفضل هذا ، @Value كائنات @Value غير قابلة للتغيير على الفور. نظرًا لأن جميع الحقول نهائية ، لا يوجد مُنشئ وسيطة. بدلاً من ذلك ، يستخدم @AllArgsConstructor . والنتيجة هي كائن وظيفي بالكامل ، غير قابل للتغيير.

لكن ثبات النظام ليس مفيدًا للغاية إذا كنت بحاجة فقط إلى إنشاء كائن باستخدام مُنشئ all-args. كما يشرح Joshua Bloch في كتابه Effective Java Programming ، يجب عليك استخدام المنشئات إذا كان لديك عدد كبير من معلمات المصمم. هنا @Builder فئة @Builder ، حيث @Builder تلقائيًا @Builder الطبقة @Builder :

 User user = User.builder() .userId(UUID.random()) .email(“grubhub@grubhub.com”) .favoriteFood(“burritos”) .favoriteFood(“dosas”) .build() 

يؤدي إنشاء المنشئ إلى تسهيل إنشاء كائنات تحتوي على عدد كبير من الوسائط وإضافة حقول جديدة في المستقبل. الأسلوب الثابت بإرجاع مثيل منشئ لتعيين كافة خصائص الكائن. بعد ذلك ، استدعاء build() بإرجاع المثيل.

يمكن استخدام @NonNull أن هذه الحقول ليست خالية عند إنشاء مثيل للكائن ؛ وإلا ، NullPointerException طرح NullPointerException . لاحظ أن حقل الصورة الرمزية @NonNull بـ @NonNull ولكن لم يتم تعيينه. الحقيقة هي أن التعليق التوضيحي @Builder.Default يشير إلى default.png بشكل افتراضي.

لاحظ أيضًا كيفية استخدام المنشئ لـ favourFood ، اسم الخاصية الوحيد في كائننا. عند وضع التعليقات التوضيحية @Singular على خاصية المجموعة ، ينشئ Lombok أساليب منشئ خاصة لإضافة عناصر إلى المجموعة بشكل فردي ، وليس لإضافة المجموعة بأكملها في نفس الوقت. يعد هذا مفيدًا بشكل خاص للاختبارات ، لأن طرق إنشاء مجموعات صغيرة في Java لا يمكن وصفها بالبساطة والسرعة.

وأخيرًا ، toBuilder = true المعلمة أسلوب المثيل toBuilder() ، الذي ينشئ كائنًا toBuilder() ممتلئًا بجميع قيم هذا المثيل. من السهل جدًا إنشاء مثيل جديد ، يتم ملؤه مسبقًا بجميع القيم من الأصل ، بحيث يبقى تغيير الحقول الضرورية فقط. هذا مفيد بشكل خاص لفئات @Value ، لأن الحقول غير قابلة للتغيير.

بعض الملاحظات تخصيص مزيد من الوظائف الخاصة للضبط. @Wither ينشئ طرق @Wither لكل خاصية. عند الإدخال ، القيمة ؛ عند الإخراج ، استنساخ المثيل بالقيمة المحدثة لحقل واحد. يسمح لك @Accessors المنشأة تلقائيًا. تقوم المعلمة fluent=true بتعطيل الحصول على وتعيين اصطلاحات للأزواج والأدوات. في بعض الحالات ، يمكن أن يكون هذا بديلاً مفيدًا لـ @Builder .

إذا لم يكن تطبيق Lombok مناسبًا لمهمتك (ونظرت إلى معدِّلات التعليقات التوضيحية) ، فيمكنك دائمًا أن تأخذ وتكتب تنفيذك الخاص. على سبيل المثال ، إذا كان لديك فئة @Data ، لكن getter واحد يحتاج إلى منطق مخصص ، فقط قم بتنفيذ هذا getter. سترى لومبوك أن التنفيذ قد تم توفيره بالفعل ، ولن يتم استبداله بالتطبيق الذي تم إنشاؤه تلقائيًا.

مع عدد قليل من التعليقات التوضيحية البسيطة ، تلقى POJO الأساسي العديد من الميزات الغنية التي تعمل على تبسيط استخدامه دون تحميل عمل مهندسينا ، دون إضاعة الوقت أو زيادة تكاليف التطوير.

إزالة رمز القالب


لومبوك مفيد ليس فقط لـ POJO: يمكن تطبيقه على أي مستوى من التطبيق. الاستخدامات التالية لـ Lombok مفيدة بشكل خاص في فئات المكونات مثل وحدات التحكم والخدمات و DAOs (كائنات الوصول إلى البيانات).

تسجيل الدخول هو مطلب أساسي لجميع أجزاء البرنامج. يجب على أي فصل يقوم بعمل ذي معنى كتابة سجل. وبالتالي ، فإن المسجل القياسي يصبح قالب لكل فئة. يعمل لومبوك على تبسيط هذا القالب إلى تعليق توضيحي واحد يقوم تلقائيًا بتعريف المسجل وإنشاء مثيل له باستخدام اسم الفئة الصحيح. هناك عدة تعليقات توضيحية مختلفة حسب هيكل المجلة.

 @Slf4j // also: @CommonsLog @Flogger @JBossLog @Log @Log4j @Log4j2 @XSlf4j public class UserService { // created automatically // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserService.class); } 

بعد إعلان المسجل ، أضف تبعياتنا:

 @Slf4j @RequiredArgsConstructor @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) public class UserService { @NonNull UserDao userDao; } 

يضيف @FieldDefaults النهائية والخاصة إلى جميع الحقول. يقوم @RequiredArgsConstructor بإنشاء مُنشئ يقوم بإعداد مثيل UserDao . يضيف @NonNull التحقق من الصحة في المُنشئ UserDao NullPointerException إذا كان مثيل UserDao صفرًا.

لكن مهلا ، هذا ليس كل شيء!


هناك العديد من المواقف التي يبذل فيها لومبوك قصارى جهده. وأظهرت الأقسام السابقة أمثلة محددة ، ولكن لومبوك يمكن أن تسهل التنمية في العديد من المجالات. فيما يلي بعض الأمثلة الصغيرة حول كيفية استخدامها بكفاءة أكبر.

على الرغم من ظهور الكلمة var في Java 9 ، فلا يزال من الممكن إعادة تعيين المتغير. يحتوي Lombok على الكلمة الأساسية val التي تطبع النوع الأخير من المتغير المحلي.

 // final Map map = new HashMap<Integer, String>(); val map = new HashMap<Integer, String>(); 

بعض الفئات ذات الوظائف الساكنة البحتة لا تهدف إلى التهيئة. طريقة واحدة لمنع إنشاء مثيل هي إعلان مُنشئ خاص يلقي استثناء. قام لومبوك بتدوين هذا القالب في التعليق التوضيحي @UtilityClass . يقوم بإنشاء مُنشئ خاص يلقي استثناءً ، وأخيراً يخرج الفصل ويجعل جميع الطرق ثابتة.

 @UtilityClass // will be made final public class UtilityClass { // will be made static private final int GRUBHUB = “ GRUBHUB”; // autogenerated by Lombok // private UtilityClass() { // throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); //} // will be made static public void append(String input) { return input + GRUBHUB; } } 

يُنتقد جافا غالبًا بسبب الفعل بسبب الاستثناءات المحددة. تعليق توضيحي منفصل من لومبوك يعمل على @SneakyThrows : @SneakyThrows . كما هو متوقع ، فإن التنفيذ صعب للغاية. لا يتم التقاط استثناءات أو حتى التفاف الاستثناءات في RuntimeException . بدلاً من ذلك ، يعتمد على حقيقة أن JVM لا يتحقق من تناسق الاستثناءات المحددة في وقت التشغيل. فقط javac يفعل هذا. لذلك ، يستخدم Lombok تحويل bytecode في وقت الترجمة لتعطيل هذا الاختيار. والنتيجة هي رمز قابل للتنفيذ.

 public class SneakyThrows { @SneakyThrows public void sneakyThrow() { throw new Exception(); } } 

جنبا إلى جنب مقارنة


تُظهر المقارنات المباشرة أفضل طريقة لحفظ رمز لومبوك. يحتوي المكون الإضافي IDE على وظيفة "de-lombok" تقوم بتحويل معظم التعليقات التوضيحية لـ Lombok تقريبًا إلى كود Java الأصلي (لا @NonNull تحويل التعليقات التوضيحية @NonNull ). وبالتالي ، فإن أي IDE مع تثبيت البرنامج المساعد سيكون قادرًا على تحويل معظم التعليقات التوضيحية إلى رمز Java الأصلي والعكس. العودة إلى فئة User لدينا.

 @Value @Builder(toBuilder = true) public class User { @NonNull UUID userId; @NonNull String email; @Singular Set<String> favoriteFoods; @NonNull @Builder.Default String avatar = “default.png”; } 

الطبقة لومبوك هي فقط 13 خطوط بسيطة وقابلة للقراءة ومفهومة. ولكن بعد تشغيل de-lombok ، يتحول الفصل إلى أكثر من مائة سطر من كود الشفرة!

 public class User { @NonNull UUID userId; @NonNull String email; Set<String> favoriteFoods; @NonNull @Builder.Default String avatar = "default.png"; @java.beans.ConstructorProperties({"userId", "email", "favoriteFoods", "avatar"}) User(UUID userId, String email, Set<String> favoriteFoods, String avatar) { this.userId = userId; this.email = email; this.favoriteFoods = favoriteFoods; this.avatar = avatar; } public static UserBuilder builder() { return new UserBuilder(); } @NonNull public UUID getUserId() { return this.userId; } @NonNull public String getEmail() { return this.email; } public Set<String> getFavoriteFoods() { return this.favoriteFoods; } @NonNull public String getAvatar() { return this.avatar; } public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof User)) return false; final User other = (User) o; final Object this$userId = this.getUserId(); final Object other$userId = other.getUserId(); if (this$userId == null ? other$userId != null : !this$userId.equals(other$userId)) return false; final Object this$email = this.getEmail(); final Object other$email = other.getEmail(); if (this$email == null ? other$email != null : !this$email.equals(other$email)) return false; final Object this$favoriteFoods = this.getFavoriteFoods(); final Object other$favoriteFoods = other.getFavoriteFoods(); if (this$favoriteFoods == null ? other$favoriteFoods != null : !this$favoriteFoods.equals(other$favoriteFoods)) return false; final Object this$avatar = this.getAvatar(); final Object other$avatar = other.getAvatar(); if (this$avatar == null ? other$avatar != null : !this$avatar.equals(other$avatar)) return false; return true; } public int hashCode() { final int PRIME = 59; int result = 1; final Object $userId = this.getUserId(); result = result * PRIME + ($userId == null ? 43 : $userId.hashCode()); final Object $email = this.getEmail(); result = result * PRIME + ($email == null ? 43 : $email.hashCode()); final Object $favoriteFoods = this.getFavoriteFoods(); result = result * PRIME + ($favoriteFoods == null ? 43 : $favoriteFoods.hashCode()); final Object $avatar = this.getAvatar(); result = result * PRIME + ($avatar == null ? 43 : $avatar.hashCode()); return result; } public String toString() { return "User(userId=" + this.getUserId() + ", email=" + this.getEmail() + ", favoriteFoods=" + this.getFavoriteFoods() + ", avatar=" + this.getAvatar() + ")"; } public UserBuilder toBuilder() { return new UserBuilder().userId(this.userId).email(this.email).favoriteFoods(this.favoriteFoods).avatar(this.avatar); } public static class UserBuilder { private UUID userId; private String email; private ArrayList<String> favoriteFoods; private String avatar; UserBuilder() { } public User.UserBuilder userId(UUID userId) { this.userId = userId; return this; } public User.UserBuilder email(String email) { this.email = email; return this; } public User.UserBuilder favoriteFood(String favoriteFood) { if (this.favoriteFoods == null) this.favoriteFoods = new ArrayList<String>(); this.favoriteFoods.add(favoriteFood); return this; } public User.UserBuilder favoriteFoods(Collection<? extends String> favoriteFoods) { if (this.favoriteFoods == null) this.favoriteFoods = new ArrayList<String>(); this.favoriteFoods.addAll(favoriteFoods); return this; } public User.UserBuilder clearFavoriteFoods() { if (this.favoriteFoods != null) this.favoriteFoods.clear(); return this; } public User.UserBuilder avatar(String avatar) { this.avatar = avatar; return this; } public User build() { Set<String> favoriteFoods; switch (this.favoriteFoods == null ? 0 : this.favoriteFoods.size()) { case 0: favoriteFoods = java.util.Collections.emptySet(); break; case 1: favoriteFoods = java.util.Collections.singleton(this.favoriteFoods.get(0)); break; default: favoriteFoods = new java.util.LinkedHashSet<String>(this.favoriteFoods.size() < 1073741824 ? 1 + this.favoriteFoods.size() + (this.favoriteFoods.size() - 3) / 3 : Integer.MAX_VALUE); favoriteFoods.addAll(this.favoriteFoods); favoriteFoods = java.util.Collections.unmodifiableSet(favoriteFoods); } return new User(userId, email, favoriteFoods, avatar); } public String toString() { return "User.UserBuilder(userId=" + this.userId + ", email=" + this.email + ", favoriteFoods=" + this.favoriteFoods + ", avatar=" + this.avatar + ")"; } } } 

سنفعل نفس UserService لفئة خدمة المستخدم.

 @Slf4j @RequiredArgsConstructor @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) public class UserService { @NonNull UserDao userDao; } 

فيما يلي نموذج نظير في كود Java القياسي.

  public class UserService { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserService.class); private final UserDao userDao; @java.beans.ConstructorProperties({"userDao"}) public UserService(UserDao userDao) { if (userDao == null) { throw new NullPointerException("userDao is marked @NonNull but is null") } this.userDao = userDao; } } 

تقييم التأثير


Grubhub لديها أكثر من مائة خدمات الأعمال تسليم الأغذية. أخذنا أحدهم وأطلقنا وظيفة "de-lombok" في البرنامج المساعد لومبوك IntelliJ. نتيجة لذلك ، تم تغيير حوالي 180 ملفًا ، ونمت قاعدة الشفرة بحوالي 18000 سطر من التعليمات البرمجية بعد إزالة 800 حالة من استخدام Lombok. في المتوسط ​​، يحفظ كل خط Lombok 23 خط Java. بهذا المعنى ، من الصعب تخيل جافا بدون لومبوك.

ملخص


يعد Lombok مساعدًا كبيرًا يقوم بتنفيذ ميزات اللغة الجديدة دون الحاجة إلى بذل الكثير من الجهد من المطور. بالطبع ، من السهل تثبيت المكوّن الإضافي بدلاً من تدريب جميع المهندسين بلغة جديدة ورمز موجود في الميناء. لومبوك ليس كلي القدرة ، لكن خارج الصندوق قوي بما يكفي للمساعدة حقًا في العمل.

ميزة أخرى من لومبوك هو أنه يحافظ على كوديباس ثابت. لدينا أكثر من مائة خدمة مختلفة وفريق موزع حول العالم ، وبالتالي فإن تماسك قواعد الشفرات يسهل توسيع نطاق الفرق ويقلل من عبء تبديل السياقات عند بدء مشروع جديد. يعمل Lombok على أي إصدار منذ Java 6 ، حتى نتمكن من الاعتماد على توفره في جميع المشاريع.

بالنسبة إلى Grubhub ، هذا أكثر من مجرد ميزات جديدة. في النهاية ، يمكن كتابة كل هذا الرمز يدويًا. لكن لومبوك يبسط الأجزاء المملة لقاعدة البيانات دون التأثير على منطق العمل. يتيح لك هذا التركيز على الأشياء المهمة حقًا بالنسبة للأعمال والأكثر إثارة للاهتمام لمطورينا. رمز قالب Monton هو مضيعة للوقت للمبرمجين والمراجعين والمشرفين. بالإضافة إلى ذلك ، نظرًا لأن هذا الرمز لم يعد مكتوبًا يدويًا ، فإنه يحل جميع فئات الأخطاء المطبعية. إن فوائد @NonNull التلقائي مقترنة بقوة @NonNull تقلل من احتمال الأخطاء وتساعد في @NonNull ، والذي يهدف إلى توصيل الطعام إلى طاولتك!

Source: https://habr.com/ru/post/ar438870/


All Articles