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


49

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 la sensazione difficile da verbalizzare che si trattava di una cattiva idea, ma OP mi chiedeva esplicitamente come farlo. Ho cercato qui perché questo potrebbe essere un problema e ho trovato questa domanda , le cui risposte sembrano prenderlo come un dato di fatto che usare la 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 generico getter e setter?


12
Se vuoi solo ridurre il boilerplate, sia Lombok che Groovy genereranno getter e setter in modo silenzioso ma sicuro.
Chrylis

2
Come consumeresti questi getter e setter in un linguaggio statico come Java? Mi sembra un non-principiante.
Esben Skov Pedersen,

@EsbenSkovPedersen Li consumeresti in modo molto simile a quello di un Map, gete putquesto è un modo piuttosto ingombrante di fare le cose.
HAEM,

2
IIRC, Lombok sintetizza getter / setter in fase di compilazione, come dettato dall'annotazione appropriata, quindi loro: non incorrono nella penalità delle prestazioni della riflessione, possono essere analizzati / incorporati staticamente, possono essere refactored (IntelliJ ha un plugin per Lombok)
Alexander,

Risposte:


117

Lati negativi della riflessione in generale

La riflessione è più difficile da comprendere rispetto al codice lineare.

Nella mia esperienza, la riflessione è una funzionalità di "livello esperto" in Java. Direi che la maggior parte dei programmatori non usa mai la riflessione in modo attivo (cioè il consumo di librerie che usano la riflessione non conta). Ciò rende il codice che lo utilizza più difficile da comprendere per questi programmatori.

Il codice di riflessione non è accessibile all'analisi statica

Supponiamo di avere un getter getFoonella mia classe e che voglio rinominarlo getBar. Se non uso alcuna riflessione, posso semplicemente cercare nella base di codice getFooe trovare ogni posto che utilizza il getter in modo da poterlo aggiornare, e anche se mi manca uno, il compilatore si lamenterà.

Ma se il posto che utilizza il getter è qualcosa di simile callGetter("Foo")e lo callGetterfa getClass().getMethod("get"+name).invoke(this), allora il metodo sopra non lo troverà e il compilatore non si lamenterà. Solo quando il codice viene effettivamente eseguito otterrai un NoSuchMethodException. E immagina il dolore che provi se quell'eccezione (che viene tracciata) viene inghiottita callGetterperché "viene utilizzata solo con stringhe codificate, non può effettivamente accadere". (Nessuno lo farebbe, qualcuno potrebbe obiettare? Tranne il fatto che l'OP ha fatto esattamente questo nella sua risposta SO. Se il campo viene rinominato, gli utenti del setter generico non se ne accorgerebbero mai, fatta eccezione per l'insetto estremamente oscuro del setter che non fa nulla in silenzio. Gli utenti del getter potrebbero, se sono fortunati, notare l'output della console dell'eccezione ignorata.)

Il codice di riflessione non viene verificato dal compilatore

Questo è fondamentalmente un grande sotto-punto di quanto sopra. Il codice di riflessione è tutto 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 provarlo.") Fondamentalmente, perdi 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 incorpora o crea una cache inline per Method.invoke. Chiamate di metodo normali 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 necessari per il codice di riflessione sono più lenti delle normali chiamate di metodo. Se trasformi quel getter economico di una riga in una bestia riflesso, potresti (non l'ho misurato) guardando diversi ordini di grandezza di rallentamento.

Aspetto negativo del getter / setter generico in particolare

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


8
Hai colpito tutti i punti principali. Praticamente una risposta perfetta. Potrei cavillare che i getter e il setter sono leggermente migliori dei campi pubblici, ma il punto più grande è corretto.
JimmyJames,

2
Vale anche la pena ricordare che i getter / setter possono essere facilmente generati anche da un IDE.
stiemannkj1,

26
+1 La logica di riflessione sul debug in un progetto di dimensioni produttive dovrebbe essere riconosciuta come tortura dalle Nazioni Unite e illegale.
errantlinguist,

4
"più difficile da capire per questi programmatori." - per questi programmatori è significativamente difficile da capire, ma è più difficile da capire per tutti, non importa quanto siano competenti.
BartoszKP,

4
"Il codice di riflessione è inaccessibile all'analisi statica" - Questo. Così importante per consentire il refactoring. E man mano che gli strumenti di analisi statica diventano più potenti (ad es. Ricerca di codice nell'ultimo Team Foundation Server), scrivere codice che li abilita diventa ancora più prezioso.
Dan J,

11

Perché getter / setter pass-through 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 della tua memoria sul campo.

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

(E rende più difficile il debug del tuo codice e danneggia l'usabilità perché ci sono più modi per utilizzarlo in modo improprio e danneggia la manutenibilità perché ora stai usando stringhe magiche piuttosto che identificatori adeguati ...)


2
Nota che la domanda è taggata Java, che ha una tale schifezza deliziosa come il concetto di fagioli. Un bean non ha campi pubblici, ma può esporre campi getX()e setX()metodi che possono essere trovati tramite reflection. I fagioli non devono necessariamente incapsulare i loro valori, potrebbero essere solo DTO. Quindi in Java, i getter sono spesso un dolore necessario. Le proprietà C # sono molto, molto più belle sotto ogni aspetto.
amon,

11
Le proprietà C # sono però getter / setter vestiti. Ma sì, il concetto di fagioli è un disastro.
Sebastian Redl,

6
@amon Giusto, C # ha preso una terribile idea e ha reso più facile l'implementazione.
JimmyJames,

5
@JimmyJames Non è un'idea terribile, davvero; puoi mantenere la compatibilità ABI nonostante le modifiche al codice. Un problema che molti non hanno, suppongo.
Casey,

3
@Casey Lo scopo di questo va oltre un commento ma la costruzione di un'interfaccia dai dettagli interni dell'implementazione è all'indietro. Conduce a un disegno procedurale. Ci sono momenti in cui avere un metodo getX è la risposta giusta ma è piuttosto raro in un codice ben progettato. Il problema con getter e setter in Java non è il codice boilerplate che li circonda, è che fanno parte di un terribile approccio progettuale. Renderlo più facile è come renderti più semplice un pugno in faccia.
JimmyJames,

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 la riflessione.


Mi sembra che questo motivo sia più importante di tutti gli altri nelle altre risposte.
Esben Skov Pedersen,

-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 quel modo, alcuni parser json o xml lo usano, se vuoi usare un DSL lo userai. Quindi dipende dallo scenario del tuo caso d'uso.

Nulla è giusto o sbagliato come dato di fatto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.