
Kami di Grubhub menggunakan Java di hampir seluruh backend. Ini adalah bahasa yang terbukti yang telah membuktikan kecepatan dan keandalannya selama 20 tahun terakhir. Tetapi selama bertahun-tahun, usia "orang tua" masih mulai mempengaruhi.
Java adalah
salah satu bahasa JVM paling populer , tetapi bukan satu-satunya. Dalam beberapa tahun terakhir, ia telah bersaing dengan Scala, Clojure dan Kotlin, yang menyediakan fungsionalitas baru dan fitur bahasa yang dioptimalkan. Singkatnya, mereka memungkinkan Anda untuk berbuat lebih banyak dengan kode yang lebih ringkas.
Inovasi dalam ekosistem JVM ini sangat menarik. Karena persaingan, Jawa terpaksa berubah agar tetap kompetitif. Jadwal rilis enam bulan yang baru dan beberapa JEP (proposal peningkatan JDK) di Java 8 (Valhalla, Inferensi Tipe Variabel lokal, Loom) adalah bukti bahwa Jawa akan tetap menjadi bahasa yang kompetitif selama bertahun-tahun.
Namun, ukuran dan skala Jawa berarti bahwa pembangunan mengalami kemajuan lebih lambat dari yang kita inginkan, belum lagi keinginan kuat untuk mempertahankan kompatibilitas dengan segala cara. Dalam perkembangan apa pun, prioritas pertama haruslah fungsi, tetapi di sini fungsi yang diperlukan telah dikembangkan terlalu lama, jika tidak, dalam bahasa. Karenanya, kami di Grubhub menggunakan Project Lombok untuk mengoptimalkan dan meningkatkan Jawa yang kami miliki saat ini. Proyek Lombok adalah plugin kompiler yang menambahkan "kata kunci" baru ke Jawa dan mengubah anotasi menjadi kode Java, mengurangi upaya pengembangan dan menyediakan beberapa fungsionalitas tambahan.
Konfigurasikan Lombok
Grubhub selalu berusaha untuk meningkatkan siklus hidup perangkat lunak, tetapi setiap alat dan proses baru memiliki biaya untuk dipertimbangkan. Untungnya, untuk menghubungkan Lombok, cukup tambahkan beberapa baris ke file gradle.
Lombok mengonversi anotasi dalam kode sumber ke pernyataan Java sebelum kompilator memprosesnya: dependensi
lombok
tidak dalam runtime, jadi menggunakan plugin tidak akan menambah ukuran rakitan. Untuk
mengkonfigurasi Lombok dengan Gradle (ini juga berfungsi dengan Maven), cukup tambahkan baris
berikut ke file
build.gradle :
plugins { id 'io.franzbecker.gradle-lombok' version '1.14' id 'java' } repositories { jcenter()
Dengan Lombok, kode sumber kami
tidak akan menjadi kode Java yang valid. Oleh karena itu, Anda perlu memasang plugin untuk IDE, jika tidak, lingkungan pengembangan tidak akan mengerti apa yang ia hadapi. Lombok mendukung semua IDE Java utama. Integrasi yang sempurna. Semua fungsi seperti "tampilkan penggunaan" dan "pergi ke implementasi" terus berfungsi seperti sebelumnya, memindahkan Anda ke bidang / kelas yang sesuai.
Lombok beraksi
Cara terbaik untuk mengenal Lombok adalah melihatnya dalam aksi. Perhatikan beberapa contoh khas.
Hidupkan kembali Objek POJO
Dengan "objek Java lama yang bagus" (POJO), kami memisahkan data dari pemrosesan untuk membuat kode lebih mudah dibaca dan merampingkan transfer jaringan. POJO sederhana memiliki beberapa bidang pribadi, serta getter dan setter yang sesuai. Mereka melakukan pekerjaan, tetapi membutuhkan banyak kode boilerplate.
Lombok membantu menggunakan POJO dengan cara yang lebih fleksibel dan terstruktur tanpa kode tambahan. Ini adalah
@Data
kami menyederhanakan POJO yang mendasarinya dengan penjelasan
@Data
:
@Data public class User { private UUID userId; private String email; }
@Data
hanyalah anotasi praktis yang berlaku beberapa anotasi Lombok sekaligus.
@ToString
menghasilkan implementasi untuk metode toString()
, yang terdiri dari representasi objek yang rapi: nama kelas, semua bidang dan nilainya.
@EqualsAndHashCode
menghasilkan implementasi equals
dan hashCode
, yang secara default menggunakan bidang non-statis dan non-stasioner, tetapi dapat disesuaikan.
@Getter / @Setter
menghasilkan getter dan setter untuk bidang pribadi.
@RequiredArgsConstructor
membuat konstruktor dengan argumen yang diperlukan, di mana bidang terakhir dan bidang dengan anotasi @NonNull
(lebih lanjut tentang ini di bawah ini).
Penjelasan ini sendiri dengan sederhana dan elegan mencakup banyak kasus penggunaan umum. Tetapi POJO tidak selalu mencakup fungsionalitas yang diperlukan.
@Data
adalah kelas yang sepenuhnya dapat dimodifikasi, penyalahgunaan yang dapat meningkatkan kompleksitas dan membatasi konkurensi, yang secara negatif mempengaruhi kemampuan bertahan aplikasi.
Ada solusi lain. Mari kita kembali ke kelas
User
kami, membuatnya tidak berubah, dan menambahkan beberapa anotasi berguna lainnya.
@Value @Builder(toBuilder = true) public class User { @NonNull UUID userId; @NonNull String email; @Singular Set<String> favoriteFoods; @NonNull @Builder.Default String avatar = “default.png”; }
Anotasi
@Value
mirip dengan
@Data
kecuali bahwa semua bidang bersifat pribadi dan final secara default, dan setter tidak dibuat. Berkat ini, objek
@Value
segera menjadi tidak berubah. Karena semua bidang bersifat final, tidak ada konstruktor argumen. Sebagai gantinya, Lombok menggunakan
@AllArgsConstructor
. Hasilnya adalah objek yang sepenuhnya berfungsi dan tidak dapat diubah.
Tetapi immutability tidak terlalu berguna jika Anda hanya perlu membuat objek menggunakan konstruktor all-args. Seperti yang dijelaskan Joshua Bloch dalam bukunya Effective Java Programming, Anda harus menggunakan pembangun jika Anda memiliki sejumlah besar parameter perancang. Di sini kelas
@Builder
, secara otomatis menghasilkan kelas batin pembangun:
User user = User.builder() .userId(UUID.random()) .email(“grubhub@grubhub.com”) .favoriteFood(“burritos”) .favoriteFood(“dosas”) .build()
Menghasilkan pembangun membuatnya mudah untuk membuat objek dengan sejumlah besar argumen dan menambahkan bidang baru di masa depan. Metode statis mengembalikan instance pembangun untuk mengatur semua properti objek. Setelah itu, panggilan untuk
build()
mengembalikan instance.
@NonNull
dapat digunakan untuk
@NonNull
bahwa bidang ini bukan nol saat membuat turunan objek, jika tidak,
NullPointerException
dilempar. Perhatikan bahwa bidang avatar dianotasi dengan
@NonNull
tetapi tidak disetel. Faktanya adalah bahwa anotasi
@Builder.Default
menunjuk ke
default.png secara default.
Perhatikan juga bagaimana pembuat menggunakan
favoriteFood
, satu-satunya nama properti di objek kami. Saat menempatkan anotasi
@Singular
pada properti koleksi, Lombok menciptakan metode pembangun khusus untuk menambahkan item ke koleksi secara individual, dan tidak menambahkan seluruh koleksi pada saat yang sama. Ini sangat baik untuk pengujian, karena cara membuat koleksi kecil di Jawa tidak bisa disebut sederhana dan cepat.
Akhirnya,
toBuilder = true
parameter menambahkan metode instance
toBuilder()
, yang membuat objek builder diisi dengan semua nilai dari instance ini. Sangat mudah untuk membuat instance baru, yang sudah diisi sebelumnya dengan semua nilai dari aslinya, sehingga tetap hanya mengubah bidang yang diperlukan saja. Ini sangat berguna untuk kelas
@Value
, karena bidang tidak dapat diubah.
Beberapa catatan selanjutnya menyesuaikan fungsi khusus setter.
@Wither
membuat metode
@Wither
untuk setiap properti. Pada input, nilai, pada output, klon instance dengan nilai yang diperbarui dari satu bidang.
@Accessors
memungkinkan Anda untuk mengonfigurasi setter yang dibuat secara otomatis.
fluent=true
parameter
fluent=true
menonaktifkan get dan set konvensi untuk getter dan setter. Dalam situasi tertentu, ini bisa menjadi pengganti yang berguna untuk
@Builder
.
Jika implementasi Lombok tidak cocok untuk tugas Anda (dan Anda melihat pengubah anotasi), maka Anda selalu dapat mengambil dan menulis implementasi Anda sendiri. Misalnya, jika Anda memiliki kelas
@Data
, tetapi satu pengambil membutuhkan logika khusus, cukup implementasikan pengambil ini. Lombok akan melihat bahwa implementasinya sudah disediakan, dan tidak akan menimpanya dengan implementasi yang dibuat secara otomatis.
Dengan hanya beberapa anotasi sederhana, basis POJO telah menerima begitu banyak fitur kaya yang menyederhanakan penggunaannya tanpa memuat pekerjaan para insinyur kami, tanpa membuang waktu atau meningkatkan biaya pengembangan.
Menghapus Kode Template
Lombok bermanfaat tidak hanya untuk POJO: Lombok dapat diterapkan di semua level aplikasi. Penggunaan Lombok berikut ini sangat berguna dalam kelas komponen seperti pengontrol, layanan, dan DAO (objek akses data).
Penebangan adalah persyaratan dasar untuk semua bagian program. Setiap kelas yang melakukan pekerjaan yang bermakna harus menulis log. Dengan demikian, logger standar menjadi templat untuk setiap kelas. Lombok menyederhanakan templat ini menjadi anotasi tunggal yang secara otomatis mengidentifikasi dan membuat instance logger dengan nama kelas yang benar. Ada beberapa anotasi yang berbeda tergantung pada struktur jurnal.
@Slf4j
Setelah mendeklarasikan logger, tambahkan dependensi kami:
@Slf4j @RequiredArgsConstructor @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) public class UserService { @NonNull UserDao userDao; }
@FieldDefaults
menambahkan pengubah akhir dan pribadi ke semua bidang.
@RequiredArgsConstructor
membuat konstruktor yang membuat instance dari
UserDao
.
@NonNull
menambahkan validasi dalam konstruktor dan
UserDao
NullPointerException
jika instance
UserDao
adalah nol.
Tapi tunggu, itu belum semuanya!
Ada banyak lagi situasi di mana Lombok melakukan yang terbaik. Bagian sebelumnya menunjukkan contoh spesifik, tetapi Lombok dapat memfasilitasi pembangunan di banyak daerah. Berikut adalah beberapa contoh kecil cara menggunakannya secara lebih efisien.
Meskipun kata kunci
var
muncul di Java 9, variabel masih dapat dipindahkan. Lombok memiliki kata kunci
val
, yang mencetak tipe terakhir dari variabel lokal.
Beberapa kelas dengan fungsi statis murni tidak dimaksudkan untuk diinisialisasi. Salah satu cara untuk mencegah instantiasi adalah dengan mendeklarasikan konstruktor pribadi yang melempar pengecualian. Lombok mengkodifikasikan templat ini di anotasi
@UtilityClass
. Ini menghasilkan konstruktor pribadi yang melempar pengecualian, akhirnya mengeluarkan kelas dan membuat semua metode statis.
@UtilityClass
Java sering dikritik karena verbositas karena pengecualian diperiksa. Anotasi Lombok terpisah memperbaikinya:
@SneakyThrows
. Seperti yang diharapkan, implementasinya cukup rumit. Itu tidak menangkap pengecualian atau bahkan membungkus pengecualian dalam
RuntimeException
. Sebaliknya, itu bergantung pada kenyataan bahwa JVM tidak memeriksa konsistensi dari pengecualian yang diperiksa pada saat dijalankan. Hanya javac yang melakukan ini. Oleh karena itu, Lombok menggunakan konversi bytecode pada waktu kompilasi untuk menonaktifkan pemeriksaan ini. Hasilnya adalah kode yang dapat dieksekusi.
public class SneakyThrows { @SneakyThrows public void sneakyThrow() { throw new Exception(); } }
Perbandingan berdampingan
Perbandingan langsung paling baik menunjukkan berapa banyak kode yang disimpan Lombok. Plugin IDE memiliki fungsi "de-lombok" yang kira-kira mengubah sebagian besar anotasi Lombok ke kode Java asli (anotasi
@NonNull
tidak dikonversi). Dengan demikian, setiap IDE dengan plugin yang dipasang akan dapat mengubah sebagian besar anotasi menjadi kode Java asli dan sebaliknya. Kembali ke kelas
User
kami.
@Value @Builder(toBuilder = true) public class User { @NonNull UUID userId; @NonNull String email; @Singular Set<String> favoriteFoods; @NonNull @Builder.Default String avatar = “default.png”; }
Kelas Lombok hanya 13 baris sederhana, mudah dibaca, dan dimengerti. Tetapi setelah menjalankan de-lombok, kelas berubah menjadi lebih dari seratus baris kode boilerplate!
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 + ")"; } } }
Kami akan melakukan hal yang sama untuk kelas
UserService
.
@Slf4j @RequiredArgsConstructor @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) public class UserService { @NonNull UserDao userDao; }
Berikut ini adalah contoh mitra dalam kode Java standar.
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; } }
Peringkat efek
Grubhub memiliki lebih dari seratus layanan bisnis pengiriman makanan. Kami mengambil salah satunya dan meluncurkan fungsi "de-lombok" di plugin Lombok IntelliJ. Akibatnya, sekitar 180 file berubah, dan basis kode bertambah sekitar 18.000 baris kode setelah menghapus 800 kasus penggunaan Lombok. Rata-rata, setiap jalur Lombok menyimpan 23 jalur Jawa. Dengan efek ini, sulit membayangkan Jawa tanpa Lombok.
Ringkasan
Lombok adalah penolong hebat yang mengimplementasikan fitur bahasa baru tanpa membutuhkan banyak usaha dari pengembang. Tentu saja, lebih mudah untuk menginstal plugin daripada melatih semua insinyur dalam bahasa baru dan port kode yang ada. Lombok tidak mahakuasa, tetapi di luar kotak cukup kuat untuk benar-benar membantu dalam pekerjaan.
Keuntungan lain Lombok adalah mempertahankan basis kode yang konsisten. Kami memiliki lebih dari seratus layanan berbeda dan tim terdistribusi di seluruh dunia, sehingga koherensi basis kode memfasilitasi penskalaan tim dan mengurangi beban beralih konteks ketika memulai proyek baru. Lombok berfungsi untuk semua versi sejak Java 6, sehingga kami dapat mengandalkan ketersediaannya di semua proyek.
Bagi Grubhub, ini lebih dari sekadar fitur baru. Pada akhirnya, semua kode ini
dapat ditulis secara manual. Tetapi Lombok menyederhanakan bagian kode basis yang membosankan tanpa memengaruhi logika bisnis. Ini memungkinkan Anda untuk fokus pada hal-hal yang sangat penting bagi bisnis dan yang paling menarik bagi pengembang kami. Kode template Monton adalah buang-buang waktu untuk programmer, pengulas, dan pengelola. Selain itu, karena kode ini tidak lagi ditulis secara manual, ini menghilangkan seluruh kelas kesalahan ketik. Manfaat
@NonNull
otomatis yang dikombinasikan dengan kekuatan
@NonNull
mengurangi kemungkinan kesalahan dan membantu pengembangan kami, yang bertujuan untuk mengirimkan makanan ke meja Anda!