Apakah kebiasaan buruk untuk (berlebihan) menggunakan refleksi?

16

Apakah praktik yang baik untuk menggunakan refleksi jika sangat mengurangi jumlah kode boilerplate?

Pada dasarnya ada trade-off antara kinerja dan mungkin keterbacaan di satu sisi dan abstraksi / otomatisasi / pengurangan kode boilerplate di sisi lain.

Sunting: Berikut adalah contoh penggunaan refleksi yang disarankan .

Untuk memberi contoh, anggaplah ada kelas abstrak Baseyang memiliki 10 bidang dan memiliki 3 subclass SubclassA, SubclassBdan SubclassCmasing-masing dengan 10 bidang yang berbeda; mereka semua adalah kacang sederhana. Masalahnya adalah bahwa Anda mendapatkan dua Basejenis referensi dan Anda ingin melihat apakah objek yang sesuai memiliki jenis (sub) yang sama dan sama.

Sebagai solusi ada solusi mentah di mana Anda pertama kali memeriksa apakah jenisnya sama dan kemudian memeriksa semua bidang atau Anda dapat menggunakan refleksi dan melihat secara dinamis apakah mereka dari jenis yang sama dan beralih ke semua metode yang dimulai dengan "dapatkan" (konvensi). lebih dari konfigurasi), panggil mereka pada kedua objek dan sebut sama pada hasil.

boolean compare(Base base1, Base, base2) {
    if (base1 instanceof SubclassA && base2 instanceof SubclassA) { 
         SubclassA subclassA1 = (SubclassA) base1;
         SubclassA subclassA2 = (SubclassA) base2;
         compare(subclassA1, subclassA2);
    } else if (base1 instanceof SubclassB && base2 instanceof SubclassB) {
         //the same
    }
    //boilerplate
}

boolean compare(SubclassA subA1, SubclassA subA2) {
    if (!subA1.getField1().equals(subA2.getField1)) {
         return false;
    }
    if (!subA1.getField2().equals(subA2.getField2)) {
         return false;
    }
    //boilerplate
}

boolean compare(SubclassB subB1, SubclassB subB2) {
    //boilerplate
}

//boilerplate

//alternative with reflection 
boolean compare(Base base1, Base base2) {
        if (!base1.getClass().isAssignableFrom(base2.getClass())) {
            System.out.println("not same");
            System.exit(1);
        }
        Method[] methods = base1.getClass().getMethods();
        boolean isOk = true;
        for (Method method : methods) {
            final String methodName = method.getName();
            if (methodName.startsWith("get")) {
                Object object1 = method.invoke(base1);
                Object object2 = method.invoke(base2);
                if(object1 == null || object2 == null)  {
                    continue;
                }
                if (!object1.equals(object2)) {
                    System.out.println("not equals because " + object1 + " not equal with " + object2);
                    isOk = false;
                }
            }
        }

        if (isOk) {
            System.out.println("is OK");
        }
}
m3th0dman
sumber
20
Terlalu sering menggunakan sesuatu adalah kebiasaan buruk.
Tulains Córdova
1
@ user61852 Benar, terlalu banyak kebebasan mengarah pada kediktatoran. Beberapa orang Yunani kuno sudah tahu tentang ini.
ott--
6
“Terlalu banyak air akan berdampak buruk bagimu. Jelas, terlalu banyak kuantitas yang berlebihan — itulah artinya! ”- Stephen Fry
Jon Purdy
4
"Kapan pun Anda menemukan diri Anda menulis kode formulir" jika objek bertipe T1, maka lakukan sesuatu, tetapi jika itu bertipe T2, maka lakukan sesuatu yang lain, "tampar diri Anda sendiri. Javapractices.com/topic/TopicAction.do?Id = 31
rm5248

Jawaban:

25

Refleksi dibuat untuk tujuan tertentu, untuk menemukan fungsionalitas kelas yang tidak diketahui pada waktu kompilasi, mirip dengan apa yang dilakukan dlopendan dlsymfungsinya dalam C. Setiap penggunaan di luar itu harus banyak diteliti.

Pernahkah terpikir oleh Anda bahwa desainer Java sendiri mengalami masalah ini? Itu sebabnya praktis setiap kelas memiliki equalsmetode. Kelas yang berbeda memiliki definisi kesetaraan yang berbeda pula. Dalam beberapa keadaan objek yang diturunkan bisa sama dengan objek dasar. Dalam beberapa keadaan, kesetaraan dapat ditentukan berdasarkan bidang pribadi tanpa getter. Kamu tidak tahu.

Itu sebabnya setiap objek yang menginginkan kesetaraan khusus harus menerapkan equalsmetode. Akhirnya, Anda ingin meletakkan objek ke dalam set, atau menggunakannya sebagai indeks hash, maka Anda harus equalstetap menerapkannya . Bahasa lain melakukannya secara berbeda, tetapi Java menggunakan equals. Anda harus tetap berpegang pada konvensi bahasa Anda.

Juga, kode "boilerplate", jika dimasukkan ke dalam kelas yang benar, cukup sulit untuk dikacaukan. Refleksi menambah kompleksitas tambahan, yang berarti peluang tambahan untuk bug. Dalam metode Anda, misalnya, dua objek dianggap sama jika satu kembali nulluntuk bidang tertentu dan yang lainnya tidak. Bagaimana jika salah satu getter Anda mengembalikan salah satu objek Anda, tanpa yang sesuai equals? Anda if (!object1.equals(object2))akan gagal. Juga membuatnya rawan bug adalah fakta bahwa refleksi jarang digunakan, jadi programmer tidak terbiasa dengan gotchasnya.

Karl Bielefeldt
sumber
12

Terlalu sering menggunakan refleksi mungkin tergantung pada bahasa yang digunakan. Di sini Anda menggunakan Java. Dalam hal itu, refleksi harus digunakan dengan hati-hati karena seringkali hanya solusi untuk desain yang buruk.

Jadi, Anda membandingkan kelas yang berbeda, ini adalah masalah yang sempurna untuk metode utama . Perhatikan bahwa instance dari dua kelas yang berbeda tidak boleh dianggap sama. Anda dapat membandingkan untuk kesetaraan hanya jika Anda memiliki instance dari kelas yang sama. Lihat /programming/27581/overriding-equals-and-hashcode-in-java untuk contoh bagaimana menerapkan perbandingan kesetaraan dengan benar.

Sulthan
sumber
16
+1 - Sebagian dari kita percaya SETIAP penggunaan refleksi adalah bendera merah yang menunjukkan desain yang buruk.
Ross Patterson
1
Paling mungkin dalam hal ini solusi yang didasarkan pada persamaan diinginkan, tetapi sebagai ide umum apa yang salah dengan solusi refleksi? Sebenarnya ini sangat umum dan metode yang sama tidak perlu ditulis secara eksplisit di setiap kelas dan subkelas (walaupun mereka dapat dengan mudah dihasilkan oleh IDE yang bagus).
m3th0dman
2
@RossPatterson Mengapa?
m3th0dman
2
@ m3th0dman Karena ini mengarah ke hal-hal seperti compare()metode Anda dengan asumsi bahwa setiap metode yang dimulai dengan "get" adalah pengambil dan aman dan pantas untuk dipanggil sebagai bagian dari operasi perbandingan. Ini merupakan pelanggaran dari definisi antarmuka yang dimaksudkan objek, dan sementara itu mungkin bijaksana, itu hampir selalu salah.
Ross Patterson
4
@ m3th0dman melanggar enkapsulasi - kelas dasar harus mengakses atribut dalam subkelasnya. Bagaimana jika mereka pribadi (atau rajin giat)? Bagaimana dengan polimorfisme? Jika saya memutuskan untuk menambahkan subclass lain dan saya ingin melakukan perbandingan secara berbeda di dalamnya? Yah, kelas dasar sudah melakukannya untukku dan aku tidak bisa mengubahnya. Bagaimana jika para getter malas memuat sesuatu? Apakah saya ingin metode perbandingan melakukannya? Bagaimana saya tahu metode yang dimulai dengan getpengambil dan bukan metode kustom mengembalikan sesuatu?
Sulthan
1

Saya pikir Anda memiliki dua masalah di sini.

  1. Berapa banyak kode dinamis vs statis yang harus saya miliki?
  2. Bagaimana cara mengekspresikan versi kesetaraan khusus?

Kode dinamis vs statis

Ini adalah pertanyaan abadi dan jawabannya sangat tegas.

Di satu sisi kompiler Anda sangat pandai menangkap semua jenis kode buruk. Ini melakukan ini melalui berbagai bentuk analisis, Analisis jenis menjadi yang umum. Ia tahu bahwa Anda tidak dapat menggunakan Bananaobjek dalam kode yang mengharapkan a Cog. Ini memberitahu Anda ini melalui kesalahan kompilasi.

Sekarang hanya dapat melakukan ini ketika dapat menyimpulkan kedua Jenis yang diterima, dan Jenis yang diberikan dari konteks. Seberapa banyak yang dapat disimpulkan, dan seberapa umum kesimpulan itu, sangat tergantung pada bahasa yang digunakan. Java dapat menyimpulkan tipe informasi melalui mekanisme seperti Inheritance, Interfaces, dan Generics. Jarak tempuh memang bervariasi, beberapa bahasa lain menyediakan lebih sedikit mekanisme, dan beberapa menyediakan lebih banyak. Itu masih bermuara pada apa yang kompiler dapat ketahui benar.

Di sisi lain kompiler Anda tidak dapat memprediksi bentuk kode asing, dan kadang-kadang algoritma umum dapat diekspresikan pada banyak jenis yang tidak dapat dengan mudah diekspresikan menggunakan sistem tipe bahasa. Dalam kasus ini kompilator tidak selalu dapat mengetahui hasil sebelumnya, dan bahkan mungkin tidak dapat mengetahui pertanyaan apa yang harus diajukan. Refleksi, antarmuka, dan kelas Object adalah cara Java menangani masalah ini. Anda harus memberikan pemeriksaan dan penanganan yang benar, tetapi bukan tidak sehat untuk memiliki kode semacam ini.

Apakah akan membuat kode Anda sangat spesifik, atau sangat umum berarti masalah yang Anda coba tangani. Jika Anda dapat dengan mudah mengekspresikannya menggunakan tipe sistem, lakukanlah. Biarkan kompiler bermain dengan kekuatannya dan membantu Anda. Jika sistem jenis tidak mungkin diketahui sebelumnya (kode asing), atau sistem jenisnya kurang cocok untuk implementasi umum dari algoritma Anda maka refleksi (dan cara dinamis lainnya) adalah alat yang tepat untuk digunakan.

Perlu diketahui bahwa melangkah keluar dari jenis sistem bahasa Anda mengejutkan. Bayangkan berjalan ke teman Anda dan memulai percakapan dalam bahasa Inggris. Tiba-tiba menjatuhkan beberapa kata dari Spanyol, Prancis, dan Kanton yang mengekspresikan pikiran Anda dengan tepat. Konteks akan memberi tahu teman Anda banyak, tetapi mereka juga mungkin tidak tahu bagaimana menangani kata-kata yang mengarah ke segala macam kesalahpahaman. Apakah menangani kesalahpahaman itu lebih baik daripada menjelaskan ide-ide itu dalam bahasa Inggris menggunakan lebih banyak kata?

Kesetaraan Kustom

Sementara saya mengerti bahwa Java sangat bergantung pada equalsmetode untuk umumnya membandingkan dua objek yang tidak selalu cocok dalam konteks yang diberikan.

Ada cara lain, dan itu juga standar Java. Itu disebut pembanding .

Mengenai bagaimana Anda menerapkan pembanding Anda akan bergantung pada apa yang Anda bandingkan, dan bagaimana.

  • Itu dapat diterapkan ke dua objek apa pun terlepas dari equalsimplementasi spesifik mereka .
  • Itu bisa menerapkan metode perbandingan umum (berbasis refleksi) untuk menangani dua objek.
  • Fungsi perbandingan boilerplate dapat ditambahkan untuk tipe objek yang umum dibandingkan, pemberian keselamatan tipe dan optimalisasi.
Kain0_0
sumber
1

Saya lebih suka menghindari pemrograman reflektif sebanyak mungkin karena itu

  • membuat kode lebih sulit untuk diperiksa secara statis oleh kompiler
  • membuat kode lebih sulit untuk dipikirkan
  • membuat kode lebih sulit untuk diperbaiki

Ini juga jauh lebih sedikit performanya daripada pemanggilan metode sederhana; dulu lebih lambat oleh urutan besarnya atau lebih.

Memeriksa Kode Statis

Setiap kode reflektif mencari kelas dan metode menggunakan string; pada contoh asli, ia mencari metode apa pun yang dimulai dengan "get"; itu akan mengembalikan getter tetapi metode lain sebagai tambahan, seperti "gettysburgAddress ()". Aturan bisa diperketat dalam kode, tetapi intinya tetap bahwa itu adalah pemeriksaan runtime ; sebuah IDE dan kompiler tidak dapat membantu. Secara umum saya tidak suka kode "diketik ketat" atau "terobsesi primitif".

Sulit untuk Berpikir Tentang

Kode reflektif lebih verbose daripada panggilan metode sederhana. Lebih banyak kode = lebih banyak bug, atau setidaknya lebih banyak potensi bug, lebih banyak kode untuk dibaca, untuk diuji, dll. Lebih sedikit lebih banyak.

Sulit Diperbaiki

Karena kode ini berbasis string / dinamis; segera setelah refleksi ada di tempat Anda tidak dapat refactor kode dengan kepercayaan 100% menggunakan alat refactoring IDE karena IDE tidak dapat mengambil penggunaan reflektif.

Pada dasarnya, hindari refleksi dalam kode umum jika memungkinkan; mencari desain yang lebih baik.

David Kerr
sumber
0

Definisi sesuatu yang berlebihan terlalu buruk, bukan? Jadi mari kita singkirkan (over) untuk saat ini.

Apakah Anda menyebut penggunaan refleksi internal yang luar biasa berat oleh semi merupakan kebiasaan buruk?

Spring menjinakkan pantulan dengan menggunakan anotasi - begitu pula Hibernate (dan mungkin puluhan / ratusan alat lainnya).

Ikuti pola-pola itu jika Anda menggunakannya dalam kode Anda sendiri. Gunakan anotasi untuk memastikan IDE pengguna Anda masih dapat membantu mereka (Bahkan jika Anda adalah satu-satunya "Pengguna" kode Anda, penggunaan refleksi yang tidak hati-hati mungkin akan kembali menggigit Anda di pantat pada akhirnya).

Namun tanpa mempertimbangkan bagaimana kode Anda akan digunakan oleh pengembang, bahkan penggunaan refleksi paling sederhana mungkin berlebihan.

Bill K
sumber
0

Saya pikir sebagian besar jawaban ini melewatkan intinya.

  1. Ya, Anda mungkin harus menulis equals()dan hashcode(), seperti dicatat oleh @KarlBielefeldt.

  2. Tapi, untuk kelas dengan banyak bidang, ini bisa menjadi boilerplate yang membosankan.

  3. Jadi, itu tergantung .

    • Jika Anda hanya perlu sama dengan dan kode hash jarang , itu pragmatis, dan mungkin ok, untuk menggunakan perhitungan refleksi tujuan umum. Setidaknya sebagai lulus pertama yang cepat dan kotor.
    • tetapi jika Anda membutuhkan jumlah yang sama, misalnya objek-objek ini dimasukkan ke dalam HashTables, sehingga kinerja akan menjadi masalah, Anda harus menuliskan kodenya. itu akan jauh lebih cepat.
  4. Kemungkinan lain: jika kelas Anda benar-benar memiliki banyak bidang yang menulis persamaan sama membosankan, pertimbangkan

    • menempatkan bidang ke dalam Peta.
    • Anda masih bisa menulis getter dan setter kustom
    • gunakan Map.equals()untuk perbandingan Anda. (atau Map.hashcode())

mis. ( catatan : mengabaikan cek nol, mungkin harus menggunakan Enums daripada kunci String, banyak yang tidak ditampilkan ...)

class TooManyFields {
  private HashMap<String, Object> map = new HashMap<String, Object>();

  public setFoo(int i) { map.put("Foo", Integer.valueOf(i)); }
  public int getFoo()  { return map.get("Foo").intValue(); }

  public setBar(Sttring s) { map.put("Bar", s); }
  public String getBar()  { return map.get("Bar").toString(); }

  ... more getters and setters ...

  public boolean equals(Object o) {
    return (o instanceof TooManyFields) &&
           this.map.equals( ((TooManyFields)o).map);
}
pengguna949300
sumber