Perché è una cattiva idea creare un setter generico e getter con la riflessione?

47

Qualche tempo fa ho scritto questa risposta a una domanda su come evitare di avere un getter e setter per ogni variabile mutabile . All'epoca, avevo solo un'intuizione difficile da verbalizzare che si trattava di una cattiva idea, ma OP chiedeva esplicitamente come farlo. Ho cercato qui perché questo potrebbe essere un problema e ho trovato questo domanda , le cui risposte sembrano prendere come un dato che l'uso della riflessione a meno che non sia assolutamente necessario è una cattiva pratica.

Quindi, qualcuno può verbalizzare perché è un dato di fatto che la riflessione dovrebbe essere evitata, in particolare nel caso del getter generico e setter?

    
posta HAEM 09.10.2017 - 16:39
fonte

4 risposte

114

Svantaggi della riflessione in generale

La riflessione è più difficile da capire rispetto al codice della linea retta.

Nella mia esperienza, la riflessione è una funzionalità "a livello di esperti" in Java. Direi che la maggior parte dei programmatori non usa mai la reflection attivamente (cioè le librerie che consumano che usano la reflection non contano). Questo rende il codice più difficile da capire per questi programmatori.

Il codice di riflessione non è accessibile all'analisi statica

Supponiamo di avere un getter getFoo nella mia classe e voglio rinominarlo in getBar . Se non utilizzo alcuna riflessione, posso solo cercare il codice base per getFoo e troverò ogni posto che usa il getter in modo da poterlo aggiornare, e anche se ne manchi uno, il compilatore si lamenterà.

Ma se il posto che usa il getter è qualcosa come callGetter("Foo") e callGetter fa getClass().getMethod("get"+name).invoke(this) , allora il metodo sopra non lo troverà, e il compilatore non si lamenterà. Solo quando il codice è effettivamente eseguito otterrai una NoSuchMethodException . E immagina il dolore in cui ti trovi se quell'eccezione (che è tracciata) viene inghiottita da callGetter perché "è usata solo con stringhe hard-coded, non può realmente accadere". (Nessuno lo farebbe, qualcuno potrebbe obiettare? Tranne che l'OP ha esattamente quello nella sua risposta SO. Se il campo viene rinominato, gli utenti del setter generico non lo noterebbero mai, tranne che per l'errore estremamente oscuro del setter che non fa nulla silenziosamente Gli utenti del getter potrebbero, se sono fortunati, notare l'output della console dell'eccezione ignorata.)

Il codice di riflessione non è controllato dal compilatore

Questo è fondamentalmente un grande sotto-punto di quanto sopra. Il codice di riflessione è tutto su Object . I tipi sono controllati in fase di esecuzione. Gli errori vengono rilevati dai test unitari, ma solo se si dispone di copertura. ("È solo un getter, non ho bisogno di testarlo.") Fondamentalmente, hai perso il vantaggio usando Java su Python che ti ha guadagnato in primo luogo.

Il codice di riflessione non è disponibile per l'ottimizzazione

Forse non in teoria, ma in pratica non troverai una JVM che incorpori o crei una cache in linea per Method.invoke . Le chiamate al metodo normale sono disponibili per tali ottimizzazioni. Questo li rende molto più veloci.

Il codice di riflessione è solo lento in generale

La ricerca del metodo dinamico e il controllo del tipo necessario per il codice di riflessione sono più lenti delle normali chiamate di metodo. Se trasformi quella getter a una linea a buon mercato in una bestia da riflessione, potresti (non l'ho misurato) osservare diversi ordini di grandezza di rallentamento.

Lato negativo del getter / setter generico

specifico

Questa è solo una cattiva idea, perché la tua classe ora non ha più incapsulamento. Ogni campo è accessibile. Potresti anche renderli tutti pubblici.

    
risposta data 09.10.2017 - 16:57
fonte
11

Perché i get-through getter / setter sono un abominio che non fornisce alcun valore. Non forniscono alcun incapsulamento poiché gli utenti possono ottenere i valori effettivi tramite le proprietà. Sono YAGNI perché raramente cambierai mai l'implementazione del tuo spazio di archiviazione sul campo.

E perché questo è molto meno performante. Almeno in C #, un getter / setter si allinea direttamente a una singola istruzione CIL. Nella tua risposta, stai sostituendo quella con 3 chiamate di funzione, che a loro volta devono caricare i metadati per riflettere sopra. In genere ho utilizzato 10 x come regola generale per i costi di riflessione, ma quando ho cercato i dati, una delle prime risposte includeva questa risposta che si avvicinava di più a 1000x.

(E rende più difficile il debug del codice e danneggia l'usabilità perché ci sono molti modi per utilizzarlo in modo errato e danneggia la manutenibilità perché ora usi stringhe magiche piuttosto che identificatori appropriati ...)

    
risposta data 09.10.2017 - 16:55
fonte
8

Oltre agli argomenti già presentati.

Non fornisce l'interfaccia prevista.

Gli utenti si aspettano di chiamare foo.getBar () e foo.setBar (valore), non foo.get ("bar") e foo.set ("bar", valore)

Per fornire l'interfaccia che gli utenti si aspettano, è necessario scrivere un getter / setter separato per ogni proprietà. Una volta fatto ciò non ha senso usare il reflection.

    
risposta data 10.10.2017 - 14:18
fonte
-4

Naturalmente non è una cattiva idea, è solo che in un normale mondo java è una cattiva idea (quando vuoi solo un getter o setter). Ad esempio alcuni framework (spring mvc) o librares lo usano già (jstl) in questo modo, alcuni parser di json o xml lo stanno usando, se si vuole usare un DSL lo si utilizzerà. Quindi dipende dallo scenario del caso d'uso.

Nulla è giusto o sbagliato come un dato di fatto.

    
risposta data 09.10.2017 - 20:36
fonte

Leggi altre domande sui tag