Kelas tertutup. Semantik vs kinerja

Mungkin bukan saya sendiri setelah membaca dokumentasi tentang kelas-kelas tersegel saya berpikir: β€œOke. Mungkin suatu hari akan berguna. ” Kemudian, ketika dalam pekerjaan saya, saya bertemu beberapa tugas di mana saya berhasil menggunakan alat ini, saya berpikir: β€œTidak buruk. Anda harus sering memikirkan aplikasi. " Dan akhirnya, saya menemukan deskripsi kelas tugas dalam buku Java Efektif (Joshua Bloch, 3) (ya, dalam buku tentang Jawa).

Mari kita lihat satu aplikasi dan mengevaluasinya dalam hal semantik dan kinerja.


Saya pikir semua orang yang bekerja dengan UI pernah bertemu implementasi interaksi UI dengan layanan melalui beberapa negara , di mana salah satu atributnya adalah beberapa jenis penanda . Mekanisme pemrosesan negara berikutnya dalam implementasi seperti itu, biasanya secara langsung tergantung pada penanda yang ditentukan. Sebagai contoh, implementasi dari kelas Negara :

class State( val type: Type, val data: String?, val error: Throwable? ) { enum class Type { LOADING, ERROR, EMPTY, DATA } } 

Kami membuat daftar kerugian dari implementasi semacam itu (coba sendiri)
Kata-kata dari bab 23 dari buku "Memilih hierarki kelas untuk menandai kelas". Saya mengusulkan untuk berkenalan dengannya.

  1. Konsumsi memori untuk atribut yang diinisialisasi hanya untuk tipe tertentu. Faktornya bisa signifikan dalam volume besar. Situasi ini diperburuk jika objek default dibuat untuk mengisi atribut.
  2. Beban semantik yang berlebihan. Pengguna kelas perlu memonitor untuk tipe apa, atribut apa yang tersedia.
  3. Dukungan rumit dalam kelas logika bisnis. Misalkan implementasi di mana suatu objek dapat melakukan beberapa operasi pada datanya. Kelas semacam itu akan terlihat seperti pemanen , dan menambahkan tipe atau operasi baru bisa menjadi sulit.


Memproses status baru mungkin terlihat seperti ini:

 fun handleState(state: State) { when(state.type) { State.Type.LOADING -> onLoading() State.Type.ERROR -> state.error?.run(::onError) ?: throw AssertionError("Unexpected error state: $state") State.Type.EMPTY -> onEmpty() State.Type.DATA -> state.data?.run(::onData) ?: throw AssertionError("Unexpected data state: $state") } } fun onLoading() {} fun onError(error: Throwable) {} fun onEmpty() {} fun onData(data: String) {} 

Harap dicatat bahwa untuk negara-negara seperti ERROR dan DATA, kompiler tidak dapat menentukan keamanan menggunakan atribut, sehingga pengguna harus menulis kode yang berlebihan. Perubahan dalam semantik hanya dapat dideteksi saat runtime.

Kelas tertutup


gambar

Dengan refactoring sederhana, kita dapat membagi Negara kita menjadi sekelompok kelas:

 sealed class State //    stateless  -     singleton object Loading : State() data class Error(val error: Throwable) : State() //  ,     ,  stateless  -  singleton object Empty : State() data class Data(val data: String) : State() 

Di sisi pengguna, kami mendapatkan pemrosesan status, di mana aksesibilitas atribut akan ditentukan pada tingkat bahasa, dan penyalahgunaan akan menyebabkan kesalahan pada tahap kompilasi:

 fun handleState(state: State) { when(state) { Loading -> onLoading() is Error -> onError(state.error) Empty -> onEmpty() is Data -> onData(state.data) } } 

Karena hanya atribut signifikan yang ada dalam salinan, kita dapat berbicara tentang menghemat memori dan, yang terpenting, meningkatkan semantik. Pengguna kelas tersegel tidak perlu mengimplementasikan aturan secara manual untuk bekerja dengan atribut tergantung pada penanda jenis , ketersediaan atribut dipastikan dengan pemisahan menjadi jenis.

Apakah semua ini gratis?


Spoiler
Tidak, tidak gratis.

Pengembang Java yang mencoba Kotlin pasti telah melihat kode yang didekompilasi untuk melihat seperti apa istilah Java Kotlin. Ekspresi dengan kapan akan terlihat seperti ini:

 public static final void handleState(@NotNull State state) { Intrinsics.checkParameterIsNotNull(state, "state"); if (Intrinsics.areEqual(state, Loading.INSTANCE)) { onLoading(); } else if (state instanceof Error) { onError(((Error)state).getError()); } else if (Intrinsics.areEqual(state, Empty.INSTANCE)) { onEmpty(); } else if (state instanceof Data) { onData(((Data)state).getData()); } } 

Cabang dengan banyak contoh mungkin mengkhawatirkan karena stereotip tentang "tanda kode buruk" dan "dampak kinerja", tetapi kami tidak tahu. Penting untuk membandingkan kecepatan eksekusi, misalnya, menggunakan jmh .

Berdasarkan artikel "Mengukur kecepatan kode Java dengan benar" , sebuah tes pemrosesan empat negara (LOADING, ERROR, EMPTY, DATA) telah disiapkan, berikut adalah hasilnya:

 Benchmark Mode Cnt Score Error Units CompareSealedVsTagged.sealed thrpt 500 940739,966 Β± 5350,341 ops/s CompareSealedVsTagged.tagged thrpt 500 1281274,381 Β± 10675,956 ops/s 

Dapat dilihat bahwa implementasi yang disegel bekerja lebih lambat 25% (ada asumsi bahwa lag tidak akan melebihi 10-15%).

Jika pada empat tipe kita memiliki seperempat lag, dengan tipe yang meningkat (jumlah instance dari cek), lag hanya akan tumbuh. Untuk memeriksanya, kami akan menambah jumlah jenis menjadi 16 (misalkan kami telah berhasil mendapatkan hierarki yang begitu luas):

 Benchmark Mode Cnt Score Error Units CompareSealedVsTagged.sealed thrpt 500 149493,062 Β± 622,313 ops/s CompareSealedVsTagged.tagged thrpt 500 235024,737 Β± 3372,754 ops/s 

Bersama dengan penurunan produktivitas, kelambatan penjualan yang disegel meningkat menjadi β‰ˆ35% - keajaiban tidak terjadi.

Kesimpulan


Dalam artikel ini, kami tidak menemukan Amerika, dan menyegel implementasi di cabang-cabang contohnya benar-benar bekerja lebih lambat daripada perbandingan tautan.


Namun demikian, beberapa pemikiran perlu disuarakan:

  • dalam kebanyakan kasus, kami ingin bekerja dengan semantik kode yang baik, mempercepat pengembangan karena petunjuk tambahan dari IDE dan pemeriksaan kompiler - dalam kasus seperti itu, Anda dapat menggunakan kelas tersegel
  • jika Anda tidak dapat mengorbankan kinerja dalam tugas, Anda harus mengabaikan implementasi yang disegel dan menggantinya, misalnya, dengan implementasi tagget. Mungkin Anda harus sepenuhnya meninggalkan kotlin demi bahasa tingkat rendah

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


All Articles