Ci sono problemi con l'utilizzo di Reflection?


49

Non so perché, ma mi sento sempre come se stessi "barando" quando uso la riflessione - forse è a causa del colpo di performance che so di prendere.

Parte di me dice che se fa parte del linguaggio che stai usando e può realizzare quello che stai cercando di fare, allora perché non usarlo. L'altra parte di me dice che ci deve essere un modo per farlo senza usare la riflessione. Immagino che forse dipende dalla situazione.

Quali sono i potenziali problemi che devo considerare quando uso la riflessione e quanto dovrei preoccuparmi di loro? Quanto sforzo vale la pena tentare di trovare una soluzione più convenzionale?


2
Immagino che dipenda dalla lingua e dall'ambiente. Alcuni lo supportano, addirittura lo incoraggiano. A volte, lavorando in Java, desideravo un migliore supporto per la riflessione.
FrustratedWithFormsDesigner,

18
Beh, immagino che non sia così difficile distinguere tra "barare riflessione" e un uso valido di esso: quando controlli i membri privati ​​o lo usi per chiamare metodi privati ​​(per aggirare le interfacce), allora è molto probabilmente un imbroglio. Quando lo usi per interagire con tipi che altrimenti non conosci, allora probabilmente va bene (database, proxy ecc.).
Falcon,

1
Che lingua stai usando? Brainfuck non ha riflessione e Python è solo diverso.
Giobbe

1
Inoltre, non ho mai capito perché sia ​​consentito l'accesso ai metodi privati ​​attraverso la riflessione. Secondo il mio intuito, dovrebbe essere proibito.
Giorgio,

3
Difficile credere che una domanda così mal formulata abbia ottenuto 27 voti positivi senza alcuna modifica.
Aaronaught,

Risposte:


48

No, non è un imbroglio: è un modo per risolvere i problemi in alcuni linguaggi di programmazione.

Ora, spesso non è la soluzione migliore (più pulita, più semplice, più facile da mantenere). Se esiste un modo migliore, usalo davvero. Tuttavia, a volte non c'è. Oppure, se esiste, è semplicemente molto più complesso, implicando molta duplicazione del codice ecc. Che lo rende impossibile (difficile da mantenere a lungo termine).

Due esempi dal nostro attuale progetto (Java):

  • alcuni dei nostri strumenti di test utilizzano la reflection per caricare la configurazione da file XML. La classe da inizializzare ha campi specifici e il caricatore di configurazione utilizza la riflessione per abbinare l'elemento XML denominato fieldXal campo appropriato nella classe e per inizializzare quest'ultimo. In alcuni casi, può creare al volo una semplice finestra di dialogo della GUI al di fuori delle proprietà identificate. Senza riflettere, ciò richiederebbe centinaia di righe di codice in diverse applicazioni. Quindi la riflessione ci ha aiutato a mettere rapidamente insieme uno strumento semplice, senza troppe complicazioni, e ci ha permesso di concentrarci sulla parte importante (test di regressione della nostra app Web, analisi dei log del server, ecc.) Piuttosto che sull'irrilevante.
  • un modulo della nostra app Web legacy doveva esportare / importare dati da tabelle DB a fogli Excel e viceversa. Conteneva un sacco di codice duplicato, dove ovviamente le duplicazioni non erano esattamente le stesse, alcune contenevano bug ecc. Usando la riflessione, l'introspezione e le annotazioni, sono riuscito a eliminare la maggior parte della duplicazione, riducendo la quantità di codice da oltre Da 5 K a meno di 2,4 K, rendendo il codice più robusto e molto più semplice da mantenere o estendere. Ora quel modulo ha cessato di essere un problema per noi, grazie all'uso giudizioso della riflessione.

La linea di fondo è, come qualsiasi strumento potente, anche il riflesso può essere usato per spararti nel piede. Se impari quando e come (non) utilizzarlo, può offrirti soluzioni eleganti e pulite a problemi altrimenti difficili. Se lo abusi, puoi trasformare un problema altrimenti semplice in un disastro complesso e brutto.


8
+1 Reflection è incredibilmente utile per l'importazione / esportazione dei dati quando si hanno oggetti intermedi per motivi di conversione.
Ed James,

2
È anche incredibilmente utile nelle app basate sui dati: invece di utilizzare una grande quantità di if (propName = n) setN(propValue);, puoi nominare i tuoi tag XML (cioè) allo stesso modo delle proprietà del codice ed eseguire un ciclo su di essi. Questo metodo rende anche molto più semplice aggiungere proprietà in seguito.
Michael K,

La riflessione viene utilizzata pesantemente nelle interfacce Fluent, come FluentNHibernate.
Scott Whitlock,

4
@Michael: Fino a quando non decidi di rinominare una di quelle proprietà nella classe e scoprire che il tuo codice esplode. Se non hai bisogno di mantenere la comparabilità con ciò che genera il tuo programma, va bene, ma sospetto che non sia la maggior parte di noi.
Billy ONeal,

1
@Billy, non intendevo contraddirti, sono d'accordo con quello che hai scritto. Anche se, l'esempio che porti è IMHO più una sottocassa della regola generale "evitare di cambiare le interfacce pubbliche".
Péter Török,

37

Non è barare. Ma di solito è una cattiva idea nel codice di produzione per almeno i seguenti motivi:

  • Si perde la sicurezza del tipo in fase di compilazione : è utile che il compilatore verifichi che un metodo sia disponibile al momento della compilazione. Se si utilizza la riflessione, in fase di esecuzione verrà visualizzato un errore che potrebbe influire sugli utenti finali se non si esegue il test abbastanza bene. Anche se si rileva l'errore, sarà più difficile eseguire il debug.
  • Causa bug durante il refactoring - se si accede a un membro in base al suo nome (ad es. Utilizzando una stringa con codice fisso), questo non verrà modificato dalla maggior parte degli strumenti di refactoring del codice e si avrà immediatamente un bug, che potrebbe essere abbastanza difficile da rintracciare.
  • Le prestazioni sono più lenta - riflessione in fase di esecuzione sta per essere più lento di chiamate di metodo compilati staticamente / ricerche variabili. Se stai solo riflettendo di tanto in tanto, non importa, ma questo può diventare un collo di bottiglia delle prestazioni nei casi in cui stai effettuando chiamate tramite la riflessione migliaia o milioni di volte al secondo. Una volta ho ottenuto una velocità 10x in alcuni codici Clojure semplicemente eliminando ogni riflessione, quindi sì, questo è un vero problema.

Suggerirei di limitare l'uso della riflessione ai seguenti casi:

  • Per prototipazione rapida o codice "usa e getta" quando è la soluzione più semplice
  • Per casi d'uso autentici di riflessione , ad esempio strumenti IDE che consentono all'utente di esaminare i campi / metodi di un oggetto arbitrario in fase di esecuzione.

In tutti gli altri casi suggerirei di capire un approccio che eviti la riflessione. Definire un'interfaccia con i metodi appropriati e implementarla sull'insieme di classi su cui si desidera chiamare i metodi è di solito sufficiente per risolvere la maggior parte dei casi semplici.


3
+1 - ci sono diversi casi d'uso per la riflessione; ma la maggior parte delle volte vedo i programmatori che lo usano, stanno copiando i disegni con gravi difetti. Definire le interfacce e tentare di rimuovere la dipendenza dalla riflessione. Proprio come GOTO, ha i suoi usi, ma molti di loro non sono buoni. (Alcuni di loro sono buoni, ovviamente)
Billy ONeal,

3
Tieni presente che i punti di cui sopra potrebbero o meno essere applicati alla tua lingua preferita. Ad esempio, la riflessione non ha penalità di velocità in Smalltalk, né si perde la sicurezza in fase di compilazione, poiché il calcolo è completamente in ritardo.
Frank Shearar,

La riflessione in D non ha lo svantaggio di cui parli. Quindi direi che stai dando la colpa all'implementazione in alcune lingue più della stessa ri-selezione. Non so molto di smalltalk, ma secondo @Frank Shearar, non ha anche questo inconveniente.
deadalnix,

2
@deadalinx: a quale lato negativo ti riferisci? Ce ne sono molti in questa risposta. Il problema delle prestazioni è l'unica cosa che dipende dalla lingua.
Billy ONeal,

1
Vorrei aggiungere che il secondo problema - i bug causati durante il refactoring - è probabilmente il più grave. In particolare, fare affidamento su qualcosa che ha un nome particolare si interrompe istantaneamente quando cambia il nome di quella cosa. Gli errori così causati potrebbero essere meno che informativi, a meno che tu non sia molto attento.
Frank Shearar,

11

La riflessione è solo un'altra forma di meta-programmazione, e altrettanto valida, come i parametri basati sul tipo che vedete in molte lingue al giorno d'oggi. Reflection è potente e generico, e i programmi riflettenti sono un alto ordine di manutenibilità (se usati correttamente, ovviamente) e molto più che programmi puramente orientati agli oggetti o procedurali. Sì, paghi un prezzo per le prestazioni, ma sarei felice di scegliere un programma più lento che è più gestibile in molti, o addirittura nella maggior parte dei casi.


2
+1, questo mi aiuta a capire ciò che la riflessione è . Sono un programmatore di C ++ e quindi non ho mai veramente criticato la riflessione. Questo aiuta. (Anche se lo confesso dal mio punto di vista, Reflection sembra un tentativo di conciliare una carenza del linguaggio. Noi ragazzi del C ++ usiamo i template per questo. Quindi suppongo che si possa dire lo stesso di quello. :)
Greyfade del

4
Per quanto riguarda le prestazioni: in alcuni casi, è possibile utilizzare l'API reflection per riscrivere completamente il codice delle prestazioni. Ad esempio, in .NET puoi scrivere IL nell'app attuale - quindi il tuo codice "reflection" può essere veloce come qualsiasi altro codice (tranne che puoi rimuovere molto if / else / qualunque, come hai già fatto capito le regole esatte)
Marc Gravell

@greyfade: in un certo senso, i template stanno effettuando una riflessione solo in fase di compilazione. Essere in grado di farlo in fase di esecuzione ha il vantaggio di consentire la creazione di potenti funzionalità nell'applicazione senza dover ricompilare. Scambia le prestazioni per flessibilità, un compromesso accettabile più del tempo previsto (dato il tuo background C ++).
Donal Fellows,

Non capisco la tua risposta. Sembra che tu stia dicendo che i programmi che usano la riflessione sono più mantenibili di quelli che non lo fanno, come se la riflessione fosse un modello di programmazione superiore che dovrebbe soppiantare i programmi orientati agli oggetti e procedurali se le prestazioni non sono necessarie.
DavidS,

8

Sicuramente tutto dipende da cosa stai cercando di ottenere.

Ad esempio, ho scritto un'applicazione di controllo dei media che utilizza l'iniezione di dipendenza per determinare quale tipo di file multimediale (file MP3 o file JPEG) controllare. La shell doveva visualizzare una griglia contenente le informazioni pertinenti per ciascun tipo, ma non era a conoscenza di ciò che stava per visualizzare. Questo è definito nell'assembly che legge quel tipo di supporto.

Pertanto ho dovuto usare la riflessione per ottenere il numero di colonne da visualizzare e i loro tipi e nomi in modo da poter impostare correttamente la griglia. Significava anche che avrei potuto aggiornare la libreria iniettata (o crearne una nuova) senza modificare nessun altro codice o file di configurazione.

L'unico altro modo sarebbe stato quello di avere un file di configurazione che avrebbe dovuto essere aggiornato quando ho cambiato il tipo di supporto da controllare. Ciò avrebbe introdotto un altro punto di errore per l'applicazione.


1
"Sicuramente tutto dipende da cosa stai cercando di ottenere." +1
DaveShaw,

7

Reflection è uno strumento fantastico se sei un autore di librerie e quindi non hai alcuna influenza sui dati in arrivo. Una combinazione di riflessione e meta-programmazione può consentire alla tua libreria di lavorare senza problemi con chiamanti arbitrari, senza che debbano saltare attraverso cerchi di generazione di codice ecc.

Cerco comunque di scoraggiare la riflessione nel codice dell'applicazione ; a livello di app dovresti usare diverse metafore: interfacce, astrazione, incapsulamento ecc.


Tali librerie sono spesso difficili da usare e da evitare (ad esempio Spring).
Sridhar Sarnobat,

@Sridhar, quindi non hai mai usato alcuna API di serializzazione? O qualsiasi ORM / micro-ORM?
Marc Gravell

Li ho usati senza scelta mia. È stato demoralizzante incorrere in problemi che avrei potuto evitare se non mi fosse stato detto cosa fare.
Sridhar Sarnobat,

7

Reflection è fantastico per la creazione di strumenti per sviluppatori.

Poiché consente all'ambiente di build di ispezionare il codice e potenzialmente generare gli strumenti corretti per manipolare / init ispezionare il codice.

Come tecnica di programmazione generale può essere utile ma è più fragile di quanto la maggior parte della gente immagini.

Un uso davvero dello sviluppo per la riflessione (IMO) è che semplifica la scrittura di una libreria di streaming generica (purché la descrizione della classe non cambi mai (quindi diventa una soluzione molto fragile)).


In effetti, la primavera è "più fragile di quanto la maggior parte della gente immagini".
Sridhar Sarnobat,

5

L'uso della riflessione è spesso dannoso nei linguaggi OO se non utilizzato con grande consapevolezza.

Ho perso il conto di quante cattive domande ho visto sui siti StackExchange dove

  1. La lingua in uso è OO
  2. L'autore vuole scoprire quale tipo di oggetto è stato passato e quindi chiamare uno dei suoi metodi
  3. L'autore rifiuta di accettare che la riflessione sia la tecnica sbagliata e accusa chiunque lo evidenzi come "non sapendo come aiutarmi"

Ecco alcuni esempi tipici.

La maggior parte del punto di OO è questo

  1. Raggruppate funzioni che sanno come manipolare una struttura / concetto con la cosa stessa.
  2. In qualsiasi punto del tuo codice dovresti conoscere abbastanza il tipo di un oggetto per sapere quali funzioni rilevanti ha e chiedergli di chiamare la sua versione particolare di quelle funzioni.
  3. Garantisci la verità del punto precedente specificando il tipo di input corretto per ogni funzione e facendo in modo che ciascuna funzione non ne sappia di più (e non faccia più) di quanto sia rilevante.

Se in qualsiasi punto del codice, il punto 2 non è valido per un oggetto che è stato passato, uno o più di questi è vero

  1. Viene immesso un input errato
  2. Il tuo codice è mal strutturato
  3. Non hai idea di cosa diavolo stai facendo.

Gli sviluppatori scarsamente qualificati semplicemente non ottengono questo e credono che possano essere passati qualsiasi cosa in qualsiasi parte del loro codice e fare ciò che vogliono da un insieme di possibilità (codificato). Questi idioti utilizzano riflessione molto .

Per i linguaggi OO, la riflessione dovrebbe essere necessaria solo nella meta attività (caricatori di classi, iniezione di dipendenza ecc.). In questi contesti, è necessaria la riflessione perché stai fornendo un servizio generico per aiutare la manipolazione / configurazione del codice di cui non sai nulla per un motivo valido e legittimo. In quasi tutte le altre situazioni, se stai riflettendo, stai facendo qualcosa di sbagliato e devi chiederti perché questo pezzo di codice non conosce abbastanza l'oggetto che gli è stato passato.


3

Un'alternativa, nei casi in cui il dominio delle classi riflesse è ben definito, è utilizzare la riflessione insieme ad altri metadati per generare codice , anziché utilizzare la riflessione in fase di esecuzione. Faccio questo usando FreeMarker / FMPP; ci sono molti altri strumenti tra cui scegliere. Il vantaggio è che si ottiene un codice "reale" che può essere facilmente eseguito il debug, ecc.

A seconda della situazione, questo può aiutarti a rendere il codice molto più veloce o solo un sacco di codice gonfio. Evita gli svantaggi della riflessione:

  • perdita della sicurezza del tipo di tempo di compilazione
  • bug dovuti a refactoring
  • prestazioni più lente

menzionato prima.

Se la riflessione sembra ingannare, potrebbe essere perché lo stai basando su molte congetture di cui non sei sicuro e il tuo istinto ti avverte che questo è rischioso. Assicurati di fornire un modo per migliorare i metadati inerenti alla riflessione con i tuoi metadati, in cui puoi descrivere tutte le stranezze e casi speciali delle classi del mondo reale che potresti incontrare.


2

Non è un imbroglio, ma come qualsiasi strumento, dovrebbe essere usato per ciò che è destinato a risolvere. Reflection, per definizione, consente di ispezionare e modificare il codice attraverso il codice; se questo è ciò che devi fare, allora la riflessione è lo strumento per il lavoro. La riflessione riguarda tutto il meta-codice: codice che ha come target il codice (al contrario del codice normale, che ha come target i dati).

Un esempio di buon uso della riflessione sono le classi di interfaccia del servizio Web generico: un progetto tipico è quello di separare l'implementazione del protocollo dalla funzionalità di payload. Quindi hai una classe (chiamiamola T) che implementa il tuo payload e un'altra che implementa il protocollo ( P). Tè abbastanza semplice: per ogni chiamata che si desidera effettuare, è sufficiente scrivere un metodo che fa tutto ciò che dovrebbe fare. P, tuttavia, deve mappare le chiamate dei servizi Web alle chiamate di metodo. Rendere questa mappatura generica è desiderabile, perché evita la ridondanza e rende Paltamente riutilizzabile. Reflection fornisce i mezzi per ispezionare la classe Tin fase di runtime e chiamare i suoi metodi in base alle stringhe passate Pattraverso il protocollo del servizio Web, senza alcuna conoscenza della classe in fase di compilazioneT. Usando la regola 'code about code', si può sostenere che la classe Pha il codice in classe Tcome parte dei suoi dati.

Però.

Reflection ti offre anche strumenti per aggirare le restrizioni del sistema di tipi di linguaggio - teoricamente, potresti passare tutti i parametri come tipo objecte chiamare i loro metodi attraverso le riflessioni. Voilà, il linguaggio che dovrebbe imporre una forte disciplina della tipizzazione statica ora si comporta come un linguaggio tipizzato dinamicamente con associazione tardiva, solo che la sintassi è molto più elaborata. Ogni singola istanza di tale schema che ho visto finora è stata un trucco sporco e, inevitabilmente, una soluzione all'interno del sistema di tipi di linguaggio sarebbe stata possibile, e sarebbe stata più sicura, più elegante e più efficiente sotto tutti gli aspetti .

Esistono alcune eccezioni, come i controlli della GUI che possono essere associati a vari tipi di origini dati non correlate; imporre che i tuoi dati implementino una determinata interfaccia solo in modo che tu possa legarli non è realistico, e nemmeno il programmatore deve implementare un adattatore per ogni tipo di origine dati. In questo caso, utilizzare la riflessione per rilevare il tipo di origine dati e regolare l'associazione dei dati è una scelta più utile.


2

Un problema che abbiamo avuto con Reflection è quando abbiamo aggiunto Obfuscation al mix. Tutte le classi ottengono nuovi nomi e all'improvviso il caricamento di una classe o funzione con il suo nome smette di funzionare.


1

Dipende totalmente. Un esempio di qualcosa che sarebbe difficile fare a meno della riflessione sarebbe replicare ObjectListView . Genera anche il codice IL al volo.


1

La riflessione è il metodo principale per creare sistemi basati su convenzioni. Non sarei sorpreso di scoprire che è ampiamente utilizzato nella maggior parte dei framework MVC. È un componente importante negli ORM. È probabile che tu stia già utilizzando componenti creati ogni giorno.

L'alternativa per un tale uso è la configurazione, che presenta una serie di inconvenienti.


0

La riflessione può realizzare cose che semplicemente non possono essere fatte praticamente in altro modo.

Ad esempio, considera come ottimizzare questo codice:

int PoorHash(char Operator, int seed, IEnumerable<int> values) {
    foreach (var v in values) {
        seed += 1;
        switch (char) {
            case '+': seed += v; break;
            case '^': seed ^= v; break;
            case '-': seed -= v; break;
            ...
        }
        seed *= 3;
    }
    return seed;
}

Al centro del loop interno è presente un costoso test, ma per estrarlo è necessario riscrivere il loop una volta per ciascun operatore. Reflection ci consente di ottenere prestazioni alla pari con l'estrazione di quel test, senza ripetere il ciclo una dozzina di volte (e quindi sacrificando la manutenibilità). Basta generare e compilare il loop di cui hai bisogno al volo.

In realtà ho fatto questa ottimizzazione , anche se era un po 'più complicata di una situazione e i risultati sono stati sorprendenti. Un ordine di miglioramento della grandezza in termini di prestazioni e meno righe di codice.

(Nota: inizialmente ho provato l'equivalente di passare un Func invece di un carattere, ed è stato leggermente migliore, ma non quasi il riflesso 10x raggiunto.)


1
La migliore ottimizzazione è invece passare un funzione. Probabilmente verrà inserito in fase di esecuzione dal compilatore JIT ed è più gestibile della riflessione.
Billy ONeal,

Sarebbe più mantenibile, e in realtà lo provo prima, ma non è stato abbastanza veloce. La JIT sicuramente non lo stava integrando, probabilmente perché nel mio caso c'era un secondo ciclo interno sulle operazioni da eseguire.
Craig Gidney,

Inoltre, sottolineo che quello che hai lì è davvero un codice auto-modificante, che non è davvero una riflessione. Si accede tramite API di riflessione nella maggior parte delle lingue che supportano la riflessione, ma può essere fatto altrettanto semplicemente in lingue che non supportano la riflessione (ad es. Sarebbe possibile in C ++)
Billy ONeal

Volevo evitare l'esempio standard di "uguaglianza strutturale". La riflessione non è necessaria per il codice automodificante, ma sicuramente aiuta.
Craig Gidney,

Non proprio. Puoi farlo bene in linguaggi non riflessivi.
Billy ONeal,

-2

Non è affatto un imbroglio ... Piuttosto, autorizza i server delle applicazioni a eseguire le classi create dall'utente con il nome di loro scelta, fornendo quindi flessibilità all'utente, non imbrogliarle.

E se vuoi vedere il codice di qualsiasi file .class (in Java), allora ci sono diversi decompilatori disponibili gratuitamente!


La decompilazione non è una funzione di riflessione.
Billy ONeal,

sì, non lo è ... Ma intendevo dire che se hai voglia di imbrogliare mentre usi la riflessione (che ti fornisce i dettagli di qualsiasi classe in Java) di quanto ti sbagli bcoz reflection è un concetto molto utile e se ottenere i dettagli di qualsiasi classe è ciò che ti preoccupa di quello che può essere fatto facilmente con qualsiasi decompilatore ...
ANSHUL JAIN

2
La riflessione è un concetto utile in alcuni casi, sì. Ma il 99,9% delle volte lo vedo in uso, lo vedo usato per aggirare un errore di progettazione.
Billy ONeal,
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.