Ci sono problemi con l'uso di Reflection?

49

Non so perché, ma mi sento sempre come se fossi "barato" quando uso il riflesso - forse è a causa del colpo di performance che so di aver preso.

Una parte di me dice che se fa parte del linguaggio che stai utilizzando e può realizzare ciò che stai cercando di fare, allora perché non usarlo. L'altra parte di me dice, ci deve essere un modo in cui posso farlo senza usare la riflessione. Suppongo che dipenda dalla situazione.

Quali sono i potenziali problemi che devo prestare attenzione quando utilizzo la riflessione e quanto dovrei preoccuparmi di loro? Quanto impegno vale la pena spendere per cercare una soluzione più convenzionale?

    
posta P B 15.08.2011 - 16:11
fonte

14 risposte

47

No, non è 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 c'è un modo migliore, usa proprio quello. Tuttavia, a volte non c'è. O se lo è, è solo molto più complesso, coinvolge un sacco di duplicazione del codice ecc. Che lo rende impossibile da gestire (difficile da mantenere a lungo termine).

Due esempi del nostro attuale progetto (Java):

  • alcuni dei nostri strumenti di test utilizzano la reflection per caricare la configurazione dai file XML. La classe da inizializzare contiene campi specifici e il caricatore di configurazione utilizza il reflection per abbinare l'elemento XML denominato fieldX al campo appropriato della classe e per inizializzare quest'ultimo. In alcuni casi, è possibile creare al volo una semplice finestra di dialogo della GUI con le proprietà identificate. Senza riflettere, questo richiederebbe centinaia di linee di codice attraverso diverse applicazioni. Quindi la riflessione ci ha aiutato a mettere insieme un semplice strumento in modo rapido, senza troppi problemi, e ci ha permesso di concentrarci sulla parte importante (regression test della nostra app web, analisi dei log del server, ecc.) Piuttosto che irrilevante.
  • un modulo della nostra app Web legacy era destinato all'esportazione / importazione dei dati dalle tabelle DB ai 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 ad eliminare la maggior parte della duplicazione, riducendo la quantità di codice da oltre Da 5K a meno di 2,4 K, rendendo il codice più robusto e 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 al piede. Se impari quando e come (non) usarlo, può portarti soluzioni eleganti e pulite a problemi altrimenti difficili. Se la abusi, puoi trasformare un problema altrimenti semplice in un pasticcio complesso e brutto.

    
risposta data 15.08.2011 - 16:20
fonte
34

Non sta barando. 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 in fase di compilazione. Se si utilizza la riflessione, si otterrà un errore in fase di esecuzione che potrebbe influire sugli utenti finali se non si esegue correttamente il test. Anche se riscontri l'errore, sarà più difficile eseguire il debug.
  • Causa bug durante il refactoring - se stai accedendo a un membro in base al suo nome (es. usando una stringa hard-coded) allora questo non verrà modificato dalla maggior parte degli strumenti di refactoring del codice e avrai istantaneamente un bug, che potrebbe essere abbastanza difficile da rintracciare.
  • Le prestazioni sono più lente - la riflessione al runtime sarà più lenta delle chiamate di metodo compilate staticamente / delle ricerche di variabili. Se si esegue la riflessione solo occasionalmente, non importa, ma questo può diventare un collo di bottiglia per le prestazioni nei casi in cui si effettuano chiamate tramite la riflessione migliaia o milioni di volte al secondo. Una volta ho ottenuto un 10x di accelerazione in qualche codice Clojure semplicemente eliminando tutti i riflessi, quindi sì, questo è un vero problema.

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

  • Per il prototipazione rapida o il codice "throwaway" quando è la soluzione più semplice
  • Per casi d'uso di riflessione autentica , ad es. Strumenti IDE che consentono a un utente di esaminare i campi / metodi di un oggetto arbitrario in fase di runtime.

In tutti gli altri casi suggerirei di individuare un approccio che eviti la riflessione. Definire un'interfaccia con il / i metodo / i appropriato / i e implementarlo sull'insieme di classi su cui vuoi chiamare il / i metodo / i è generalmente sufficiente per risolvere i casi più semplici.

    
risposta data 15.08.2011 - 17:29
fonte
11

Reflection è solo un'altra forma di meta-programmazione, e altrettanto valida, come i parametri basati sui tipi che vedi in molte lingue al giorno d'oggi. La riflessione è potente e generica, e i programmi di riflessione sono un ordine elevato di manutenibilità (se usato correttamente, ovviamente) e molto più dei programmi puramente orientati agli oggetti o procedurali. Sì, paghi un prezzo di prestazioni, ma sarei lieto di optare per un programma più lento che è più gestibile in molti casi, o anche in molti casi.

    
risposta data 15.08.2011 - 16:21
fonte
8

Sicuramente tutto dipende da cosa stai cercando di ottenere.

Ad esempio, ho scritto un'applicazione per il controllo dei media che utilizza l'iniezione delle dipendenze per determinare quale tipo di supporto (file MP3 o file JPEG) da controllare. La shell doveva mostrare una griglia contenente le informazioni pertinenti per ciascun tipo, ma non aveva alcuna conoscenza di cosa avrebbe mostrato. Questo è definito nell'assembly che legge quel tipo di media.

Quindi ho dovuto usare il reflection per ottenere il numero di colonne da visualizzare e i loro tipi e nomi così da poter impostare correttamente la griglia. Significava anche che potevo aggiornare la libreria iniettata (o crearne una nuova) senza modificare nessun altro codice o file di configurazione.

L'unico altro modo sarebbe stato 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.

    
risposta data 15.08.2011 - 16:17
fonte
7

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

Cerco di scoraggiare dal riflesso nel codice applicazione , però; a livello di app dovresti utilizzare diverse metafore: interfacce, astrazione, incapsulamento, ecc.

    
risposta data 15.08.2011 - 20:43
fonte
6

La riflessione è fantastica per la creazione di strumenti per gli sviluppatori.

Poiché consente all'ambiente di costruzione di ispezionare il codice e potenzialmente generare gli strumenti corretti da manipolare / inizializzare il codice.

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

Un vero strumento di sviluppo per la riflessione (IMO) è che rende la scrittura di una libreria di streaming generica molto semplice (purché la descrizione della classe non cambi mai (quindi diventa una soluzione molto fragile)).

    
risposta data 15.08.2011 - 16:45
fonte
5

L'uso della riflessione è spesso dannoso nelle lingue 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 poi chiamare uno dei suoi metodi
  3. L'autore si rifiuta di accettare che la riflessione sia la tecnica sbagliata e accusa qualcuno che lo fa notare come "non sapendo come aiutarmi"

Qui sono esempi tipici.

La maggior parte del punto di OO è quella

  1. Funzioni di gruppo che sanno come manipolare una struttura / concetto con la cosa stessa.
  2. In qualsiasi punto del tuo codice dovresti sapere abbastanza sul tipo di un oggetto per sapere quali funzioni rilevanti ha e chiederle di chiamare la sua particolare versione di quelle funzioni.
  3. Ti assicuri la verità del punto precedente specificando il tipo di input corretto per ogni funzione e ogni funzione non ne conosce più (e non ne fa più) di quella pertinente.

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

  1. È stato inserito l'input sbagliato
  2. Il tuo codice è scarsamente strutturato
  3. Non hai idea di cosa diavolo stai facendo.

Gli sviluppatori scarsamente qualificati semplicemente non lo capiscono e credono di poter passare qualcosa in qualsiasi parte del loro codice e fare ciò che vogliono da un insieme di possibilità (hardcoded). Questi idioti usano la riflessione molto .

Per i linguaggi OO, la riflessione dovrebbe essere necessaria solo nella meta attività (caricatori di classi, iniezioni di dipendenze, ecc.). In quei contesti, è necessaria una riflessione perché stai fornendo un servizio generico per assistere la manipolazione / configurazione del codice di cui non sai nulla per un motivo valido e legittimo. In quasi tutte le altre situazioni, se stai cercando di riflettere, stai facendo qualcosa di sbagliato e devi chiedere a te stesso perché questo pezzo di codice non ne sa abbastanza sull'oggetto che gli è stato passato.

    
risposta data 29.01.2015 - 18:59
fonte
3

Un'alternativa, nei casi in cui il dominio delle classi riflesse è ben definito, consiste nell'utilizzare la riflessione insieme ad altri metadati per generare codice , invece di usare reflection in runtime. Lo faccio usando FreeMarker / FMPP; ci sono molti altri strumenti tra cui scegliere. Il vantaggio è che si finisce con codice "reale" che può essere facilmente debugato, ecc.

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

  • perdita della sicurezza del tipo in fase di compilazione
  • bug dovuti al refactoring
  • prestazioni più lente

menzionato in precedenza.

Se la riflessione sembra imbrogliare, potrebbe essere perché stai basando su un sacco di 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 i casi speciali delle classi del mondo reale che potresti incontrare.

    
risposta data 16.08.2011 - 02:19
fonte
2

Non è un imbroglio, ma come qualsiasi altro strumento, dovrebbe essere usato per ciò che è destinato a risolvere. La reflection, per definizione, consente di ispezionare e modificare il codice tramite codice; se questo è ciò che devi fare, la riflessione è lo strumento per il lavoro. La riflessione è tutta una questione di meta-codice: codice che indirizza il codice (in contrasto con il codice normale, che mira ai dati).

Un esempio di utilizzo di una buona riflessione sono le classi generiche dell'interfaccia del servizio Web: un tipico progetto consiste nel separare l'implementazione del protocollo dalla funzionalità del 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 vuoi fare, scrivi semplicemente un metodo che fa quello che deve fare. P , tuttavia, deve mappare le chiamate di servizio Web alle chiamate di metodo. Rendere questa mappatura generica è auspicabile, perché evita la ridondanza e rende estremamente riutilizzabile P . Reflection fornisce i mezzi per ispezionare la classe T in fase di esecuzione e chiama i suoi metodi basati su stringhe passate in P tramite il protocollo del servizio Web, senza alcuna conoscenza in fase di compilazione della classe T . Utilizzando la regola "code about code", si può sostenere che la classe P ha il codice nella classe T come parte dei suoi dati.

Comunque.

Reflection fornisce anche strumenti per aggirare le restrizioni del sistema dei tipi di linguaggio - in teoria, puoi passare tutti i parametri come object e chiamare i loro metodi attraverso i riflessi. Voilà, il linguaggio che dovrebbe applicare una strong disciplina di tipizzazione statica ora si comporta come un linguaggio tipizzato dinamicamente con un binding tardivo, solo che la sintassi è molto più elaborata. Ogni singola istanza di tale modello che ho visto fino ad ora è stata un brutto scherzo e, invariabilmente, una soluzione all'interno del sistema di tipo linguistico 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 dati a vari tipi di fonti di dati non correlati; che impone che i dati implementino una determinata interfaccia solo per consentire il collegamento dei dati, non è realistico e nessuno dei due ha il programmatore che implementa un adattatore per ciascun tipo di origine dati. In questo caso, l'uso della riflessione per rilevare il tipo di origine dati e la regolazione dell'associazione dati è una scelta più utile.

    
risposta data 15.08.2011 - 23:27
fonte
2

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

    
risposta data 16.08.2011 - 09:49
fonte
1

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

    
risposta data 15.08.2011 - 16:32
fonte
1

Reflection è il metodo principale per creare sistemi basati su convenzioni. Non sarei sorpreso di scoprire che è in uso pesantemente nella maggior parte dei framework MVC. È un componente importante negli ORM. È probabile che tu stia già utilizzando componenti costruiti con esso ogni giorno.

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

    
risposta data 29.01.2015 - 21:31
fonte
0

La riflessione può raggiungere cose che semplicemente non possono essere fatte praticamente altrimenti.

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;
}

Nel mezzo del loop interno c'è un costoso test, ma per estrarlo è necessario riscrivere il ciclo una volta per ogni operatore. La riflessione 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 ciclo di cui hai bisogno al volo.

Ho effettivamente fatto questa ottimizzazione , anche se era un un po 'più complicato di una situazione, ei risultati sono stati sorprendenti. Un miglioramento del livello di ordine delle prestazioni e un numero inferiore di righe di codice.

(Nota: inizialmente ho provato l'equivalente di passare un Func invece di un char, ed era leggermente migliore, ma non quasi la riflessione 10x raggiunta.)

    
risposta data 15.08.2011 - 18:22
fonte
-2

In nessun modo è 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 ingannandolo.

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

    
risposta data 15.08.2011 - 18:05
fonte