Mengapa itu ide yang buruk untuk membuat setter generik dan pengambil dengan refleksi?

49

Beberapa waktu yang lalu saya menulis jawaban ini untuk pertanyaan tentang bagaimana menghindari memiliki pengambil dan penyetel untuk setiap variabel yang bisa berubah. Pada saat itu, saya hanya punya firasat yang sulit untuk diverbalkan bahwa ini adalah Ide yang Buruk, tetapi OP secara eksplisit menanyakan bagaimana cara melakukannya. Saya mencari di sini tentang mengapa ini mungkin menjadi masalah, dan menemukan pertanyaan ini , yang jawabannya tampaknya menganggap bahwa menggunakan refleksi kecuali benar-benar diperlukan adalah praktik yang buruk.

Jadi, dapatkah seseorang mengatakan secara verbal mengapa refleksi harus dihindari, khususnya dalam kasus pengambil dan penyetel generik?

HAEM
sumber
12
Jika Anda hanya ingin mengurangi pelat baja, baik Lombok dan Groovy akan menghasilkan getter dan setter secara diam-diam tetapi aman.
chrylis -on strike-
2
Bagaimana Anda bahkan mengonsumsi getter dan setter ini dalam bahasa statis seperti java? Sepertinya saya bukan pemula.
Esben Skov Pedersen
@EsbenSkovPedersen Anda akan mengkonsumsinya seperti Map's getdan put, yang merupakan cara yang cukup kikuk dalam melakukan sesuatu.
HAEM
2
IIRC, Lombok mensintesis pengambil / penyetel pada waktu kompilasi, sebagaimana didiktekan oleh anotasi yang sesuai, sehingga mereka: tidak dikenai penalti kinerja refleksi, dapat dianalisis / digariskan secara statis, dapat direaktor ulang (IntelliJ memiliki plugin untuk Lombok)
Alexander

Jawaban:

117

Kerugian refleksi secara umum

Refleksi lebih sulit dipahami daripada kode garis lurus.

Dalam pengalaman saya, refleksi adalah fitur "tingkat ahli" di Jawa. Saya berpendapat bahwa sebagian besar programmer tidak pernah menggunakan refleksi secara aktif (mis. Mengkonsumsi perpustakaan yang menggunakan refleksi tidak masuk hitungan). Itu membuat kode menggunakannya lebih sulit untuk dipahami oleh para programmer ini.

Kode refleksi tidak dapat diakses untuk analisis statis

Misalkan saya memiliki seorang pengambil getFoodi kelas saya dan saya ingin mengubah nama menjadi getBar. Jika saya tidak menggunakan refleksi, saya bisa mencari basis kode getFoodan akan menemukan setiap tempat yang menggunakan pengambil sehingga saya dapat memperbaruinya, dan bahkan jika saya melewatkannya, kompiler akan mengeluh.

Tetapi jika tempat yang menggunakan pengambil adalah sesuatu seperti callGetter("Foo")dan callGettertidak getClass().getMethod("get"+name).invoke(this), maka metode di atas tidak akan menemukannya, dan kompiler tidak akan mengeluh. Hanya ketika kode tersebut benar-benar dieksekusi Anda akan mendapatkan NoSuchMethodException. Dan bayangkan rasa sakit yang Anda alami jika pengecualian itu (yang dilacak) ditelan oleh callGetterkarena "itu hanya digunakan dengan string hard-coded, itu tidak dapat benar-benar terjadi". (Tidak ada yang akan melakukan itu, seseorang mungkin berdebat? Kecuali bahwa OP melakukan hal itu dalam jawaban SO-nya. Jika bidang ini diganti namanya, pengguna setter generik tidak akan pernah melihat, kecuali bug yang sangat tidak jelas dari setter diam-diam tidak melakukan apa-apa. Pengguna pengambil mungkin, jika mereka beruntung, memperhatikan output konsol dari pengecualian yang diabaikan.)

Kode refleksi bukan tipe-diperiksa oleh kompiler

Ini pada dasarnya adalah sub-poin besar di atas. Tentang kode refleksi Object. Jenis diperiksa pada saat runtime. Kesalahan ditemukan oleh unit test, tetapi hanya jika Anda memiliki jangkauan. ("Ini hanya pengambil, saya tidak perlu mengujinya.") Pada dasarnya, Anda kehilangan keuntungan menggunakan Java lebih dari Python mendapatkan Anda di tempat pertama.

Kode refleksi tidak tersedia untuk optimasi

Mungkin tidak dalam teori, tetapi dalam praktiknya, Anda tidak akan menemukan JVM yang inline atau membuat cache inline untuk Method.invoke. Panggilan metode normal tersedia untuk optimasi seperti itu. Itu membuat mereka jauh lebih cepat.

Kode pantulan umumnya lambat

Pencarian metode dinamis dan pemeriksaan jenis yang diperlukan untuk kode refleksi lebih lambat daripada panggilan metode normal. Jika Anda mengubah rajutan satu baris murah itu menjadi binatang pantulan, Anda mungkin (saya belum mengukur ini) akan melihat beberapa urutan besarnya perlambatan.

Kekurangan dari pengambil / penyetel generik secara khusus

Itu hanya ide yang buruk, karena kelas Anda sekarang tidak memiliki enkapsulasi lagi. Setiap bidang yang dimilikinya dapat diakses. Anda mungkin juga membuat semuanya publik.

Sebastian Redl
sumber
8
Anda mencapai semua poin utama. Cukup banyak jawaban yang sempurna. Saya bisa berdalih bahwa getter dan setter sedikit lebih baik daripada bidang publik tetapi poin yang lebih besar benar.
JimmyJames
2
Perlu juga disebutkan bahwa getter / setter dapat dengan mudah dihasilkan oleh IDE juga.
stiemannkj1
26
+1 Logika refleksi debugging dalam proyek ukuran produksi harus diakui sebagai penyiksaan oleh PBB dan ilegal.
errantlinguist
4
"Lebih sulit dimengerti untuk para programmer ini." - untuk programmer ini sangat sulit untuk dipahami, tetapi lebih sulit untuk dipahami oleh semua orang, tidak peduli seberapa mahir.
BartoszKP
4
"Kode refleksi tidak dapat diakses untuk analisis statis" - Ini. Sangat penting untuk memungkinkan refactoring. Dan ketika alat analisis statis menjadi lebih kuat (mis. Pencarian kode di Team Foundation Server terbaru), menulis kode yang memungkinkannya menjadi lebih berharga.
Dan J
11

Karena pass-through getter / setters adalah kekejian yang tidak memberikan nilai. Mereka tidak menyediakan enkapsulasi karena pengguna dapat memperoleh nilai aktual melalui properti. Mereka adalah YAGNI karena Anda jarang jika akan mengubah implementasi penyimpanan bidang Anda.

Dan karena ini jauh lebih sedikit performant. Setidaknya dalam C #, pengambil / penyetel akan langsung sejalan dengan satu instruksi CIL. Dalam jawaban Anda, Anda menggantinya dengan 3 panggilan fungsi, yang pada gilirannya perlu memuat metadata untuk direfleksikan. Saya biasanya menggunakan 10x sebagai aturan praktis untuk biaya refleksi, tetapi ketika saya mencari data, salah satu jawaban pertama termasuk jawaban ini yang menyempitnya mendekati 1000x.

(Dan itu membuat kode Anda lebih sulit untuk di-debug, dan itu merusak kegunaan karena ada lebih banyak cara untuk menyalahgunakannya, dan itu merusak pemeliharaan karena Anda sekarang menggunakan string ajaib daripada pengidentifikasi yang tepat ...)

Telastyn
sumber
2
Perhatikan bahwa pertanyaan itu ditandai Java, yang memiliki omong kosong yang menyenangkan seperti konsep Kacang. Kacang tidak memiliki bidang publik, tetapi dapat mengekspos bidang melalui getX()dan setX()metode yang dapat ditemukan melalui refleksi. Kacang tidak selalu seharusnya merangkum nilai-nilai mereka, mereka mungkin hanya DTO. Jadi di Jawa, getter sering kali terasa sakit. Properti C # jauh, jauh lebih baik dalam segala hal.
amon
11
Properti C # adalah getter / setter berdandan. Tapi ya, konsep Beans adalah bencana.
Sebastian Redl
6
@amon Benar, C # mengambil ide buruk dan membuatnya lebih mudah diimplementasikan.
JimmyJames
5
@ JimmyJames Ini bukan ide yang buruk, sungguh; Anda dapat mempertahankan kompatibilitas ABI meskipun ada perubahan kode. Masalah yang tidak dimiliki banyak orang, saya kira.
Casey
3
@Casey Cakupan ini melampaui komentar tetapi membangun antarmuka dari rincian internal implementasi mundur. Ini mengarah pada desain prosedural. Ada kalanya memiliki metode getX adalah jawaban yang tepat tetapi itu jarang terjadi dalam kode yang dirancang dengan baik. Masalah dengan getter dan setter di Jawa bukan kode boilerplate di sekitar mereka, itu adalah bahwa mereka adalah bagian dari pendekatan desain yang mengerikan. Membuat itu lebih mudah seperti membuatnya lebih mudah untuk meninju wajah Anda.
JimmyJames
8

Selain argumen yang sudah disajikan.

Itu tidak menyediakan antarmuka yang diharapkan.

Pengguna berharap untuk memanggil foo.getBar () dan foo.setBar (nilai), bukan foo.get ("bar") dan foo.set ("bar", nilai)

Untuk menyediakan antarmuka yang diharapkan pengguna, Anda perlu menulis pengambil / penyetel terpisah untuk setiap properti. Setelah Anda selesai melakukannya, tidak ada gunanya menggunakan refleksi.

Peter Green
sumber
Menurut saya, alasan ini lebih penting daripada semua yang lain dalam jawaban lain.
Esben Skov Pedersen
-4

Tentu saja itu bukan ide yang buruk, itu hanya daripada di dunia java yang normal adalah ide yang buruk (ketika Anda hanya menginginkan pengambil atau penyetel). Misalnya beberapa frameworks (spring mvc) atau librares sudah menggunakannya (jstl) dengan cara itu, beberapa parser json o xml menggunakannya, jika Anda ingin menggunakan DSL Anda akan menggunakannya. Maka itu tergantung pada skenario use case Anda.

Tidak ada yang benar atau salah sebagai fakta.

sebagai fakta
sumber