Spons-Docs
Kebanyakan Sponge API menggunakan sistem Optional Java dalam mengakses objek, tetapi jika anda beum pernah menggunakan Optional
sebelumnya hal ini akan terasa seperti melakukan hal yang sedikit janggal. Anda akan tergoda untuk bertanya: ''kenapa saya perlu melakukan langkah tambahan disaat mengambil sesuatu dari objek API?''
Bagian ini memberikan ringkasan singkat dari Optional
dan menjelaskan bagaimana - dan mungkin yang lebih penting kenapa - hal tersebut digunakan diseluruh Sponge API.
Mari kita mulai dengan sedikit sejarah, dan perhatikan bagaimana pengakses - khususnya ''getters'' - yang biasanya bekerja ketika tidak menggunakan Optional
.
1. Implisit Nullable Kontrak dan Mengapa Mereka Menghisap
Anggap saja kita punya objek API yang sederhana Entitas
dengan metode getFoo()
yang akan mengembalikan Entititas nya menjadi Foo
.
Di masa dahulu, plugin kami bisa mengambil dan menggunakan Foo
dari suatu entitas menggunakan getter
seperti ini:
public void someEventHandler(Entity someEntity) {
Foo entityFoo = someEntity.getFoo();
entityFoo.bar();
}
Masalah timbul karena - saat mendesain API - kami harus mengandalkan sebuah kontrak implisit pada metode getFoo
dengan menerima baik metode nya bisa (atau tidak bisa) kembali null
. implicit contact ini bisa ditentukan dengan dua cara:
Di javadoc - ini adalah buruk karena hal itu bergantung pada plugin penulis membaca javadoc untuk metode, dan kontrak yang mungkin tidak jelas bagi penulis plugin
Menggunakan nullable anotasi - ini tidak ideal karena secara umum anotasi ini memerlukan alat yang dapat digunakan, misalnya dengan mengandalkan IDE atau compiler untuk menangani penjelasan.
Mari kita asumsikan metode getFoo()
bisa - sebagai bagian dari kontrak nya - kembali null. Dengan sendirinya ini berarti kode kita diatas tidak aman karena bisa menjadi NullPointerException
jika entityFoo
adalah null.
public void someEventHandler(Entity someEntity) {
Foo entityFoo = someEntity.getFoo();
entityFoo.bar();
}
Mari kita asumsikan pembuat plugin kami faham dengan sifat nullable dari metode getFoo
kami dan memutuskan untuk memperbaiki masalah dengan pengecekan null. Anggap saja mereka sudah menentukan lokal konstan Foo
, kode-kode yang dihasilkan akan terlihat seperti ini:
public void someEventHandler(Entity someEntity) {
Foo entityFoo = someEntity.getFoo();
if (entityFoo == null) {
entityFoo = MyPlugin.DEFAULT_FOO;
}
entityFoo.bar();
}
Pada contoh ini, penggubah plugin sadar bahwa metode tersebut bisa kembali null dan memiliki kesatuan konstan dengan default Foo
secara instan yang dapat digunakan sebagai gantinya. Tentu saja plugin bisa saja meminta short-circuit seluruhnya, atau bisa mencoba untuk mengambil Foo
dari tempat yang lain. Pesan utamanya adalah mengatur null meskipun dalam kasus yang sederhana bisa mengantarkan kepada kode spaghetti dengan cukup cepat, dan bahkan bergantung kepada penggubah plugin untuk secara eksplisit mengunjungi metode kontrak untuk memeriksa apakah pemeriksaan null dibutuhkan dari awal.
Bagaimanapun hal tersebut bukanlah satu-satunya kelemahan. Mari kita pertimbangkan API tersebut untuk jangka panjang dan anggaplah pada saat itu penggubah menuliskan plugin mereka, mereka mengunjungi metode javadoc dan menemukan bahwa metode tersebut telah dijamin tidak akan pernah kembali ke null (karena setiap Entitas selalu memiliki Foo
yang tersedia). Hebat! Tidak ada pemeriksaan null yang rumit yang yang diperlukan!
Bagaimanapun, mari sekarang kita asumsikan di versi permainan selanjutnya, pengembang permainan menghapus atau mencela konsep Foo
. Pengunggah API mengupdate API sesuai petunjuk dan menyatakan bahwa mulai dari sekarang metode getFoo()
bisa kembali nul
dan tuliskan ini kedalam metode javadoc. Sekarang timbul masalah: bahkan penggubah plugin yang rajin sekalipun yang telah mengecek metode kontrak saat pertama sekali mereka menuliskan kode mereka tanpa sengaja mengatur metode dengan tidak tepat: dengan tidak mengecek null dalam menempatkan kode-kode menggunakan Foo
setelah kembali dari getFoo
akan meningkatkan NPE.
Dengan demikian kita dapat melihat bahwa memungkinkan implisit nullable kontrak meninggalkan kita dengan pilihan yang cukup mengerikan solusi untuk memilih dari:
Penggubah plugin bisa menganggap bahwa semua metode akan kembali null dan kode mempertahankan diri karenanya, tetapi kita sudah melihat bahwa hal ini mengarah ke kode spaghetti dengan cukup cepat.
Penulis API bisa menentukan kontrak nullable implisit disetiap metode API dalam upaya menangani null sebagai permasalahan penulis plugin yang hanya memperburuk masalah sebelumnya.
API penulis dapat menegaskan bahwa setiap implisit nullable kontrak mereka menentukan akan pernah bisa diubah ke depan. Ini berarti bahwa dalam kemungkinan bahwa mereka perlu untuk menangani penghapusan fitur dari permainan dasar maka mereka harus baik:
Melempar pengecualian - tidak elegan tapi tentu lebih mudah untuk mendiagnosa dari longgar NPE yang mungkin dipicu tempat lain di codebase dan akan sulit untuk melacak
Objek ''palsu'' kembali atau nilai yang tidak benar - hal ini berarti bahwa konsumsi kode (plugin) akan terus bekerja, tetapi menciptakan beban yang semakin meningkat bagi pengembangan API kedepannya karena setiap ciri yang telah dicela akan membutuhkan pembuatan lebih banyak objek palsu lagi. Hal ini akan segera mengarah pada situasi dimana potongan besar API dipenuhi dengan objek sampah yang tujuanny hanya untuk mendukung bagian dari API yang tidak lagi bekerja.
Itu harus cukup jelas sekarang bahwa ada beberapa yang cukup besar sakit kepala yang melekat pada implisit nullable kontrak, membuat semua lebih pedih ketika API yang dimaksud adalah lapisan atas yang sangat tidak stabil dan pilihan produk. Untungnya, ada cara yang lebih baik:
2. Opsional dan Eksplisit Nullable Kontrak
Seperti disebutkan di atas, Api untuk Minecraft berada dalam situasi yang sulit. Akhirnya mereka butuhkan untuk menyediakan sebuah platform dengan jumlah yang wajar dari tersirat stabilitas di atas sebuah platform (permainan) dengan benar-benar tidak ada jumlah tersirat stabilitas. Dengan demikian setiap API untuk Minecraft perlu dirancang dengan penuh kesadaran bahwa setiap aspek dari permainan ini adalah bertanggung jawab untuk mengubah setiap saat untuk alasan apapun dengan cara apapun dibayangkan; sampai dengan dan termasuk yang dihapus sama sekali!
Volatilitas ini adalah apa yang menyebabkan masalah dengan nullable metode kontrak yang dijelaskan di atas.
Optional menyelesaikan masalah diatas dengan mengganti kontrak implisit dengan kontrak eksplisit. API tidak pernah mengatakan, ''ini adalah objek anda, kthxbai'', tetapi API menghadirkan aksesor dengan ''ini adalah kotak yang berisi atau tidak berisi objek yang anda minta, ymmv''.
Dengan menyandikan kemungkinan kembali null
kedalam kontrak eksplisit, kita mengganti konsep dari pemeriksaan null dengan konsep yang lebih bernuansa dari mungkin tidak ada. Kita juga menetapkan kontrak ini dari hari pertama.
Jadi apa artinya ini?
Singkatnya, penggubah plugin tidak perlu lagi khawatir tentang kemungkinn null
kembali lagi. Melainkan objek tertentu yang sangat mungkin tidak tersedia menjadi penyandian dengan kode plugin mereka yang sangat terstruktur. Hal ini memiliki level yang sama dengan keamanan yang melekat yang secara terus-menerus melakukan null-cek, tetapi dengan keuntungan yang lebih elegan dan kode yang mudah dibaca untuk melakukan nya.
Untuk melihat kenapa, mari kita lihat contoh diatas, diubah untuk menggunakan metode getFoo
yang mengembalikan Optional<Foo>
sebagai gantinya:
public void someEventHandler(Entity someEntity) {
Optional<Foo> entityFoo = someEntity.getFoo();
if (entityFoo.isPresent()) {
entityFoo.get().bar();
}
}
Anda bisa mencatat bahwa contoh ini terlihat sangat serupa dengan null-cek biasa, namun penggunaan Optional
sebenarnya membawa informasi sediki lebih banyak dengan jumlah kode yang sama. Sebagai contoh, hal ini tidak dibutuhkan untuk seseorang membaca kode di atas untuk memeriksa metode kontrak, hal ini jelas bahwa metode tersebut mungkin tidak mengembalikan nilai, dan penanganan ketiadaan nilai dilakukan secara eksplisit dan jelas.
Lalu apa? Kontrak eksplisit kami dalam kasus ini menghasilkan pada dasarnya jumlah kode yang sama dengan pemeriksaan null - meskipun dikontraknya di enforced oleh getter. ''Whoop de do'' anda bilang, ''lalu apa?''
Nah Optional tinju memungkinkan kita untuk mengambil beberapa tradisional lebih canggung aspek null-memeriksa dan membuat mereka lebih elegan: perhatikan kode berikut:
public void someEventHandler(Entity someEntity) {
Foo entityFoo = someEntity.getFoo().orElse(MyPlugin.DEFAULT_FOO);
entityFoo.bar();
}
Pegang telepon! Apakah kita hanya mengganti membosankan null-cek-dan-default-tugas dari contoh di atas dengan satu baris kode? Ya memang kita lakukan. Bahkan, untuk kasus penggunaan kita bahkan dapat membuang dengan tugas:
public void someEventHandler(Entity someEntity) {
someEntity.getFoo().orElse(MyPlugin.DEFAULT_FOO).bar();
}
Ini sangatlah aman selama MyPlugin.DEFAULT_FOO
selalu tersedia.
Pertimbangkan contoh berikut dengan dua entitas, menggunakan kontrak nullable yang implisit kita ingin menggunakan Foo
dari entitas yang pertama, atau jika tidak tersedia gunakan Foo
dari yang kedua entitas
, dan kembalikan ke default kami jika tidak ada yang tersedia:
public void someEventHandler(Entity someEntity, Entity entity2) {
Foo entityFoo = someEntity.getFoo();
if (entityFoo == null) {
entityFoo = entity2.getFoo();
}
if (entityFoo == null) {
entityFoo = MyPlugin.DEFAULT_FOO;
}
entityFoo.bar();
}
Menggunakan Optional
kita bisa menyandi ini jauh lebih bersih sebagai:
public void someEventHandler(Entity someEntity, Entity entity2) {
someEntity.getFoo().orElse(entity2.getFoo().orElse(MyPlugin.DEFAULT_FOO)).bar();
}
Ini hanyalah puncak dari gunung es Optional
. Dalam java 8 Optional
juga mendukung Consumer
dan Supplier
antarmuka, mengizinkan lambas digunakan untuk kegagalan absent. Contoh penggunaan untuk hal tersebut bisa ditemukan di halaman Contoh penggunaan.
Catatan
Penjelasan lain yang rasional dibalik menghindari referensi null bisa ditemukan di Guava: Penjelasan Menggunakan Dan Menghindari Null <https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained/>._. Hati-hati kelas ``Optional` guava yang disebutkan didalam artikel terkait berbeda dari kelas java java.util.Optional
dan oleh sebab itu akan memiliki nama metode yang berbeda dari yang dipergunakan disini.