Spons-Docs

Much of SpongeAPI makes use of Java's Optional system on object accessors, but if you've never used Optional before this might seem like a bit of a peculiar way of doing things. You might be tempted to ask: "why do I need to perform an extra step when fetching something from an API object?"

This section gives a brief summary of Optional and explains how - and perhaps more importantly why - it's used throughout SpongeAPI.

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();
}

In this example, the plugin author is aware that the method can return null and has a constant available with a default instance of Foo which can be used instead. Of course, the plugin could just short-circuit the call entirely, or it could attempt to fetch Foo from somewhere else. The key message is that handling nulls even in simple cases can lead to spaghetti code quite quickly, and moreover relies on the plugin author to explicitly visit the method's contract to check whether a null check is necessary in the first place.

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.

Thus, we can see that allowing implicit nullable contracts leaves us with a selection of pretty awful solutions to choose from:

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

  • API penulis dapat menentukan implisit nullable kontrak pada setiap metode API, dalam upaya untuk membuat null penanganan plugin penulis masalah, yang hanya memperburuk pendekatan 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

As mentioned above, APIs for Minecraft are in a difficult situation. Ultimately, they need to provide a platform with a reasonable amount of implied stability atop a platform (the game) with absolutely no amount of implied stability. Thus, any API for Minecraft needs to be designed with full awareness that any aspect of the game is liable to change at any time for any reason in any way imaginable; up to and including being removed altogether!

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.

So, what does this mean?

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();
}

This is merely the tip of the Optional iceberg. In java 8 Optional also supports the Consumer and Supplier interfaces, allowing lambdas to be used for absent failover. Usage examples for those can be found on the Contoh penggunaan page.

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.