Existe-t-il des problèmes d'utilisation de Reflection?

49

Je ne sais pas pourquoi, mais j'ai toujours l'impression de "tricher" lorsque j'utilise la réflexion - c'est peut-être à cause de la performance que j'ai subie, je sais que je prends.

Une partie de moi dit, si cela fait partie du langage que vous utilisez et que cela peut accomplir ce que vous essayez de faire, alors pourquoi ne pas l'utiliser. L'autre partie de moi dit qu'il doit y avoir un moyen de faire cela sans utiliser de réflexion. Je suppose que cela dépend peut-être de la situation.

Quels sont les problèmes potentiels que je dois surveiller lorsque j'utilise la réflexion et quel est mon intérêt? Combien d'effort vaut la peine de dépenser pour essayer de trouver une solution plus conventionnelle?

PB
la source
2
Je suppose que cela dépend de la langue et de l'environnement. Certains le soutiennent, l’encouragent même. Parfois, lorsque je travaillais en Java, je souhaitais un meilleur support de réflexion.
FrustratedWithFormsDesigner
18
Eh bien, je suppose qu’il n’est pas si difficile de faire la distinction entre "réflexion de triche" et une utilisation valide de celle-ci: lorsque vous inspectez des membres privés ou que vous l’utilisez pour appeler des méthodes privées (pour contourner des interfaces), il est très probable que vous trichez. Lorsque vous l'utilisez pour interagir avec des types que vous ne connaissez pas, sinon c'est probablement correct (liaison de données, proxy, etc.).
Falcon
1
Quelle langue utilisez-vous? Brainfuck n'a pas de réflexion et Python est juste différent.
Job
1
De plus, je n'ai jamais compris pourquoi il était permis d'accéder à des méthodes privées par la réflexion. Selon mon intuition, cela devrait être interdit.
Giorgio
3
Difficile de croire qu'une question aussi mal formulée ait reçu 27 votes positifs sans révision.
Aaronaught

Réponses:

48

Non, ce n'est pas de la triche, c'est un moyen de résoudre des problèmes dans certains langages de programmation.

Aujourd'hui, ce n'est souvent pas la meilleure solution (la plus propre, la plus simple, la plus facile à gérer). S'il existe un meilleur moyen, utilisez-le bien. Cependant, parfois il n'y en a pas. Ou, s’il y en a un, c’est beaucoup plus complexe, il faut beaucoup de duplication de code, etc., ce qui le rend infaisable (difficile à maintenir à long terme).

Deux exemples de notre projet actuel (Java):

  • Certains de nos outils de test utilisent la réflexion pour charger la configuration à partir de fichiers XML. La classe à initialiser a des champs spécifiques et le chargeur de configuration utilise la réflexion pour faire correspondre l'élément XML nommé fieldXau champ approprié de la classe et pour initialiser ce dernier. Dans certains cas, il peut créer une boîte de dialogue simple à partir des propriétés identifiées à la volée. Sans réflexion, cela prendrait des centaines de lignes de code dans plusieurs applications. La réflexion nous a donc permis de mettre au point un outil simple, rapide et sans problème, et de nous concentrer sur l’essentiel (tests de régression de notre application Web, analyse des journaux de serveur, etc.) plutôt que sur des questions non pertinentes.
  • Un module de notre ancienne application Web était destiné à exporter / importer des données de tables de bases de données vers des feuilles Excel et inversement. Il contenait beaucoup de code dupliqué, où bien sûr les duplications n'étaient pas exactement les mêmes, certaines contenaient des bogues, etc. En réfléchissant, en faisant une introspection et en annotant, j'ai réussi à éliminer la majeure partie de la duplication, en réduisant la quantité de code de plus de 5K à moins de 2,4K, tout en rendant le code plus robuste et plus facile à maintenir ou à étendre. Maintenant, ce module a cessé d’être un problème pour nous - grâce à l’utilisation judicieuse de la réflexion.

Comme tout outil puissant, la réflexion peut également être utilisée pour se tirer une balle dans le pied. Si vous apprenez quand et comment (ne pas) l'utiliser, cela peut vous apporter des solutions élégantes et propres à des problèmes autrement difficiles. Si vous en abusez, vous pouvez transformer un problème simple en un désordre complexe et laid.

Péter Török
la source
8
+1 La réflexion est extrêmement utile pour l’import / export de données lorsque vous avez des objets intermédiaires pour des raisons de conversion.
Ed James
2
C'est également extrêmement utile dans les applications basées sur les données: au lieu d'utiliser une pile massive if (propName = n) setN(propValue);, vous pouvez nommer vos balises XML de la même manière que vos propriétés de code et les exécuter en boucle. Cette méthode simplifie également l'ajout de propriétés ultérieurement.
Michael K
La réflexion est fortement utilisée dans les interfaces Fluent, comme FluentNHibernate.
Scott Whitlock
4
@Michael: Jusqu'à ce que vous décidiez de renommer l'une de ces propriétés de la classe et de constater que votre code explose. Si vous n'avez pas besoin de maintenir la comparabilité avec ce que votre programme génère, c'est bien, mais je suppose que ce n'est pas la plupart d'entre nous.
Billy ONeal
1
@Billy, je ne voulais pas te contredire, je suis d'accord avec ce que tu as écrit. Bien que l'exemple que vous présentiez soit à mon humble avis, il s'agisse plutôt d'un sous-cas de la règle générale "éviter de modifier les interfaces publiques".
Péter Török
37

Ce n'est pas tricher. Mais c'est généralement une mauvaise idée dans le code de production pour au moins les raisons suivantes:

  • Vous perdez la sécurité du type de compilation - il est utile de demander au compilateur de vérifier qu’une méthode est disponible au moment de la compilation. Si vous utilisez la réflexion, vous obtiendrez une erreur au moment de l'exécution qui pourrait affecter les utilisateurs finaux si vous ne testez pas assez bien. Même si vous détectez l'erreur, il sera plus difficile de déboguer.
  • Cela provoque des bogues lors du refactoring - si vous accédez à un membre en fonction de son nom (par exemple, en utilisant une chaîne codée en dur), cela ne sera pas modifié par la plupart des outils de refactoring de code et vous aurez instantanément un bogue, qui peut être assez complexe. difficile à traquer.
  • Les performances sont plus lentes - la réflexion au moment de l'exécution sera plus lente que les appels de méthode / recherches de variable compilés statiquement. Si vous ne réfléchissez qu'à l'occasion, cela n'a pas d'importance, mais cela peut devenir un goulot d'étranglement dans les cas où vous passez des appels par réflexion des milliers ou des millions de fois par seconde. Une fois, j’ai eu une accélération 10x dans certains codes Clojure simplement en éliminant toute réflexion, alors oui, c’est un vrai problème.

Je suggérerais de limiter l'utilisation de la réflexion aux cas suivants:

  • Pour le prototypage rapide ou le code "jetable" lorsqu'il s'agit de la solution la plus simple
  • Pour des cas d'utilisation authentiques , par exemple, un outil IDE permettant à un utilisateur d'examiner les champs / méthodes d'un objet arbitraire au moment de l'exécution.

Dans tous les autres cas, je suggérerais de trouver une approche qui évite la réflexion. Définir une interface avec la ou les méthodes appropriées et l'implémenter sur l'ensemble des classes sur lesquelles vous souhaitez appeler les méthodes est généralement suffisant pour résoudre les cas les plus simples.

Mikera
la source
3
+1 - il existe plusieurs cas d'utilisation pour la réflexion; mais la plupart du temps, je vois des programmeurs l’utiliser, ils tapissent des dessins dotés de graves défauts. Définissez les interfaces et essayez de supprimer la dépendance à la réflexion. Tout comme GOTO, il a ses utilisations, mais la plupart d'entre elles ne sont pas bonnes. (Certains sont bons, bien sûr)
Billy ONeal
3
N'oubliez pas que les points ci-dessus peuvent ou non s'appliquer à votre langue préférée. Par exemple, la réflexion n’entraîne aucune pénalité liée à la vitesse dans Smalltalk, et vous ne perdez pas non plus la sécurité lors de la compilation, car le calcul est entièrement tardif.
Frank Shearar
La réflexion en D ne présente pas l’inconvénient que vous avez mentionné. Je dirais donc que vous blâmez la mise en œuvre dans certaines langues plutôt que la refection elle-même. Je ne connais pas grand chose au sujet de smalltalk, mais selon @Frank Shearar, cet inconvénient ne l’est pas non plus.
deadalnix
2
@deadalinx: À quel inconvénient faites-vous référence? Il y en a plusieurs dans cette réponse. Le problème de performances est la seule chose qui dépend de la langue.
Billy ONeal
1
Je devrais ajouter que le deuxième problème - les bugs causés lors de la refactorisation - est probablement le plus grave. En particulier, le fait de s'appuyer sur un nom portant un nom particulier se dissipe instantanément lorsque son nom change. Les erreurs ainsi causées peuvent être moins qu'informatives, à moins que vous ne soyez très prudent.
Frank Shearar
11

La réflexion est juste une autre forme de méta-programmation, et tout aussi valide, que les paramètres basés sur le type que vous voyez dans la plupart des langues de nos jours. La réflexion est puissante et générique, et les programmes réflexifs représentent un ordre de maintenabilité élevé (lorsqu'ils sont utilisés correctement, bien sûr) et plus encore que les programmes purement orientés objet ou procéduraux. Oui, vous payez un prix basé sur la performance, mais je choisirais volontiers un programme plus lent, plus facile à maintenir dans de nombreux cas, voire dans la plupart des cas.

DeadMG
la source
2
+1, cela me aide à comprendre ce que la réflexion est . Je suis un programmeur C ++ et je n'ai donc jamais vraiment réfléchi à la réflexion. CA aide. (Bien que j'avoue que de mon point de vue, Reflection semble être une tentative de concilier une déficience de langage. Nous, les gars de C ++, utilisons des modèles pour cela. Je suppose que vous pourriez en dire
autant
4
Concernant les performances - dans certains cas, vous pouvez utiliser l’API de réflexion pour réécrire intégralement le code de performances. Par exemple, dans .NET, vous pouvez écrire IL dans l’application actuelle. Ainsi, votre code de "réflexion" peut être aussi rapide que tout autre code (sauf que vous pouvez supprimer beaucoup de if / else / que vous compris les règles exactes)
Marc Gravell
@ greyfade: Dans un sens, les modèles effectuent une réflexion uniquement lors de la compilation. Pouvoir le faire au moment de l'exécution présente l'avantage de permettre la création de puissantes fonctionnalités dans l'application sans avoir à recompiler. Vous échangez les performances contre de la flexibilité, un compromis acceptable plus souvent que prévu (en fonction de votre expérience en C ++).
Donal Fellows
Je ne comprends pas ta réponse. Vous semblez dire que les programmes qui utilisent la réflexion sont plus faciles à gérer que ceux qui ne le font pas, comme si la réflexion était un modèle de programmation supérieur qui devrait supplanter les programmes orientés objet et procéduraux si les performances ne sont pas nécessaires.
DavidS
8

Tout dépend sûrement de ce que vous essayez de réaliser.

Par exemple, j'ai écrit une application de vérification des supports qui utilise l'injection de dépendance pour déterminer le type de support (fichiers MP3 ou fichiers JPEG) à vérifier. Le shell devait afficher une grille contenant les informations pertinentes pour chaque type, mais il ne savait pas ce qu'il allait afficher. Ceci est défini dans l'assembly qui lit ce type de support.

Par conséquent, je devais utiliser la réflexion pour obtenir le nombre de colonnes à afficher, ainsi que leurs types et leurs noms, afin de pouvoir configurer correctement la grille. Cela signifiait également que je pouvais mettre à jour la bibliothèque injectée (ou en créer une nouvelle) sans modifier aucun autre code ou fichier de configuration.

La seule autre manière aurait été d'avoir un fichier de configuration qui aurait besoin d'être mis à jour lorsque je changerais de type de média en cours de vérification. Cela aurait introduit un autre point d'échec pour l'application.

ChrisF
la source
1
"Tout dépend sûrement de ce que vous essayez de réaliser." +1
DaveShaw
7

La réflexion est un outil formidable si vous êtes un auteur de bibliothèque et n’avez donc aucune influence sur les données entrantes. Une combinaison de réflexion et de méta-programmation peut permettre à votre bibliothèque de travailler de manière transparente avec des appelants arbitraires, sans avoir à passer par des étapes de génération de code, etc.

J'essaie toutefois de décourager toute réflexion dans le code de l' application ; au niveau de l'application, vous devez utiliser différentes métaphores - interfaces, abstraction, encapsulation, etc.

Marc Gravell
la source
De telles bibliothèques sont souvent difficiles à utiliser et à éviter (par exemple, Spring).
Sridhar Sarnobat
@Sridhar, vous n'avez donc jamais utilisé d'API de sérialisation? Ou n'importe quel ORM / micro-ORM?
Marc Gravell
Je ne les ai pas utilisés par moi-même. Cela a été démoralisant de rencontrer des problèmes que j'aurais pu éviter si on ne me disait pas quoi faire.
Sridhar Sarnobat
7

La réflexion est fantastique pour créer des outils pour les développeurs.

Comme cela permet à votre environnement de construction d’inspecter le code et de générer potentiellement les outils appropriés pour manipuler / init inspecter le code.

En tant que technique de programmation générale, cela peut être utile, mais il est plus fragile que la plupart des gens ne l’imaginent.

Une utilisation vraiment intéressante pour la réflexion (IMO) est qu’il est très simple d’écrire une bibliothèque de streaming générique (tant que votre description de classe ne change pas (elle devient alors une solution très fragile)).

Martin York
la source
En effet, le printemps est "plus fragile que la plupart des gens ne l'imaginent".
Sridhar Sarnobat
5

L'utilisation de la réflexion est souvent nuisible dans les langages OO si elle n'est pas utilisée avec une grande conscience.

J'ai perdu le compte du nombre de mauvaises questions que j'ai vues sur les sites StackExchange où

  1. La langue utilisée est OO
  2. L'auteur veut savoir quel type d'objet a été passé et ensuite appeler l'une de ses méthodes.
  3. L’auteur refuse d’accepter le fait que la réflexion soit une mauvaise technique et reproche à quiconque de le signaler comme "ne sachant pas comment m'aider"

Voici sont des exemples typiques.

La plupart du point de OO est que

  1. Vous regroupez des fonctions qui savent manipuler une structure / un concept avec la chose elle-même.
  2. En tout point de votre code, vous devez en savoir assez sur le type d'un objet pour connaître les fonctions pertinentes dont il dispose et lui demander d'appeler sa version particulière de ces fonctions.
  3. Vous garantissez la vérité du point précédent en spécifiant le type d'entrée correct pour chaque fonction et en laissant chaque fonction en savoir plus (et n'en faire pas plus) que ce qui est pertinent.

Si, à un moment quelconque de votre code, le point 2 n'est pas valide pour un objet que vous avez été passé, alors un ou plusieurs d'entre eux sont vrais.

  1. La mauvaise entrée est alimentée
  2. Votre code est mal structuré
  3. Vous n'avez aucune idée de ce que vous faites.

Les développeurs peu qualifiés ne comprennent tout simplement pas cela et croient qu'on peut transmettre n'importe quoi dans n'importe quelle partie de leur code et qu'ils font ce qu'ils veulent à partir d'un ensemble de possibilités (codées en dur). Ces idiots utilisent beaucoup la réflexion .

Pour les langages orientés objet, la réflexion ne devrait être nécessaire que dans la méta-activité (chargeurs de classes, injection de dépendance, etc.). Dans ces contextes, il est nécessaire de réfléchir car vous fournissez un service générique pour faciliter la manipulation / configuration du code dont vous ne connaissez rien pour une bonne et légitime raison. Dans presque toutes les situations, si vous souhaitez réfléchir, vous faites quelque chose de mal et vous devez vous demander pourquoi ce morceau de code n'en sait pas assez sur l'objet qui lui a été transmis.

itsbruce
la source
3

Une alternative, dans les cas où le domaine des classes réfléchies est bien défini, consiste à utiliser la réflexion avec d'autres métadonnées pour générer du code , au lieu d'utiliser la réflexion au moment de l'exécution. Je le fais en utilisant FreeMarker / FMPP; il y a beaucoup d'autres outils à choisir. L'avantage, c'est que vous vous retrouvez avec du "vrai" code qui peut facilement être débogué, etc.

En fonction de la situation, cela peut vous aider à créer du code beaucoup plus rapide - ou juste beaucoup de code. Cela évite les inconvénients de la réflexion:

  • perte de sécurité lors de la compilation
  • bugs dus au refactoring
  • performance plus lente

mentionné plus tôt.

Si la réflexion vous donne l'impression de tricher, c'est peut-être parce que vous vous basez sur beaucoup de conjectures dont vous n'êtes pas sûr, et que votre intestin vous avertit que c'est risqué. Assurez-vous de fournir un moyen d'améliorer les métadonnées inhérentes à la réflexion avec vos propres métadonnées, où vous pourrez décrire toutes les bizarreries et tous les cas particuliers des classes du monde réel que vous pourriez rencontrer.

Ed Staub
la source
2

Ce n'est pas de la triche, mais comme tout outil, il devrait être utilisé pour ce qu'il est censé résoudre. La réflexion, par définition, vous permet d'inspecter et de modifier le code par le biais de code; Si c'est ce que vous devez faire, alors la réflexion est l'outil indispensable. La réflexion est centrée sur le méta-code: code qui cible le code (par opposition au code normal qui cible les données).

Les classes génériques d’interface de service Web constituent un exemple d’utilisation judicieuse de la réflexion: une conception typique consiste à séparer la mise en œuvre du protocole de la fonctionnalité de charge utile. Vous avez donc une classe (appelons-la T) qui implémente votre charge utile, et une autre qui implémente le protocole ( P). Test assez simple: pour chaque appel que vous voulez faire, écrivez simplement une méthode qui fait ce qu’elle est supposée faire. PToutefois, vous devez mapper les appels de service Web sur les appels de méthode. Rendre ce mappage générique est souhaitable, car il évite les redondances et est Phautement réutilisable. Reflection fournit les moyens d'inspecter une classe Tau moment de l'exécution et d'appeler ses méthodes en fonction de chaînes passées Pvia le protocole de service Web, sans aucune connaissance de la compilation lors de la compilation.T. En utilisant la règle 'code about code', on peut affirmer que la classe Pa le code en classe Tdans ses données.

Pourtant.

Reflection vous fournit également des outils permettant de contourner les restrictions du système de types du langage. En théorie, vous pouvez transmettre tous les paramètres en tant que type objectet appeler leurs méthodes par réflexions. Voilà, le langage censé imposer une discipline de typage statique forte se comporte désormais comme un langage typé dynamiquement avec une liaison tardive, à la différence près que la syntaxe est beaucoup plus élaborée. Chaque exemple d'un tel modèle que j'ai vu jusqu'à présent a été un sale jeu et, invariablement, une solution dans le système de types de langue aurait été possible, et aurait été plus sûre, plus élégante et plus efficace à tous égards. .

Il existe quelques exceptions, telles que les contrôles d'interface graphique pouvant être liés aux données à différents types de sources de données non liées; Exiger que vos données implémentent une certaine interface afin que vous puissiez lier les données n'est pas réaliste, et le programmeur n'a pas non plus à implémenter d'adaptateur pour chaque type de source de données. Dans ce cas, il est plus utile d’utiliser la réflexion pour détecter le type de source de données et d’ajuster la liaison de données.

tdammers
la source
2

Un problème que nous avons eu avec Reflection est quand nous avons ajouté Obfuscation au mélange. Toutes les classes reçoivent de nouveaux noms et le chargement d'une classe ou d'une fonction par son nom cesse de fonctionner.

Carra
la source
1

Cela dépend totalement. Un exemple de chose qui serait difficile à faire sans réflexion serait de répliquer ObjectListView . Il génère également du code IL à la volée.

Tangurena
la source
1

La réflexion est la principale méthode de création de systèmes basés sur des conventions. Je ne serais pas surpris de constater qu'il est fortement utilisé dans la plupart des frameworks MVC. C'est un composant majeur dans les ORM. Les chances sont, vous utilisez déjà des composants construits avec elle tous les jours.

L'alternative pour une telle utilisation est la configuration, qui présente son propre ensemble d'inconvénients.

Chris McCall
la source
0

La réflexion peut permettre d'atteindre des objectifs impossibles à réaliser autrement.

Par exemple, considérez comment optimiser ce code:

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

Il y a un test coûteux en plein milieu de la boucle interne, mais son extraction nécessite de réécrire la boucle une fois pour chaque opérateur. La réflexion nous permet d’obtenir des performances équivalentes à celles de l’extraction de ce test, sans répéter la boucle une douzaine de fois (sans pour autant sacrifier la facilité de maintenance). Il suffit de générer et de compiler la boucle dont vous avez besoin à la volée.

En fait, j'ai optimisé cette situation , même si la situation était un peu plus compliquée et les résultats ont été étonnants. Une amélioration de l'ordre de grandeur des performances et moins de lignes de code.

(Remarque: au départ, j'ai essayé l'équivalent de passer un Func à la place d'un caractère, ce qui était légèrement meilleur, mais presque pas la réflexion 10x obtenue.)

Craig Gidney
la source
1
La meilleure optimisation consiste à passer un foncteur à la place. Le compilateur JIT sera probablement en ligne au moment de l'exécution et sera plus facile à gérer que la réflexion.
Billy ONeal
Ce serait plus facile à maintenir, et je l’essayais d’abord, mais ce n’était pas assez rapide. Le JIT ne l'a définitivement pas aligné, probablement parce que dans mon cas, il y avait une deuxième boucle interne sur les opérations à effectuer.
Craig Gidney
De plus, je signale que ce que vous avez là est un code à auto-modification, qui n'est pas vraiment un reflet. On y accède via des API de réflexion dans la plupart des langages qui prennent en charge la réflexion, mais cela peut être fait tout aussi simplement dans des langages ne prenant pas en charge la réflexion (par exemple, cela serait possible en C ++)
Billy ONeal
Je voulais éviter l'exemple standard «d'égalité structurelle». La réflexion n'est pas nécessaire pour auto-modifier le code, mais cela aide certainement.
Craig Gidney
Pas vraiment. Vous pouvez le faire parfaitement dans des langages sans réflexion.
Billy ONeal
-2

Aucun moyen de tricher ... Cela permet plutôt aux serveurs d'applications d'exécuter les classes créées par l'utilisateur avec le nom de leur choix, offrant ainsi une flexibilité à l'utilisateur, pas de le tromper.

Et si vous voulez voir le code de n’importe quel fichier .class (en Java), plusieurs décompilateurs sont disponibles gratuitement!

ANSHUL JAIN
la source
La décompilation n'est pas une fonction de réflexion.
Billy ONeal
oui, ce n'est pas ... Mais je voulais dire que si vous avez envie de tricher en utilisant la réflexion (qui vous donne des détails sur n'importe quelle classe en java), alors vous avez tort. La réflexion de bcoz est un concept très utile. la classe est ce qui vous préoccupe que cela peut être fait facilement avec n’importe quel décompilateur ...
ANSHUL JAIN
2
La réflexion est un concept utile dans certains cas, oui. Mais 99,9% du temps où je le vois être utilisé, je le vois utilisé pour pirater une erreur de conception.
Billy ONeal