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.

../../_images/optionals1.png

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.

../../_images/optionals2.png

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''.

../../_images/optionals3.png

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.