web-dev-qa-db-fra.com

Y a-t-il des problèmes avec Reflection?

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

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

Quels sont les problèmes potentiels que je dois rechercher lors de l'utilisation de la réflexion et dans quelle mesure dois-je m'en préoccuper? Combien d'efforts valent-ils la peine d'essayer de trouver une solution plus conventionnelle?

51
P B

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

Maintenant, ce n'est souvent pas la meilleure solution (la plus propre, la plus simple, la plus facile à entretenir). S'il y a une meilleure façon, utilisez-la en effet. Cependant, parfois il n'y en a pas. Ou s'il y en a, il est tellement plus complexe, impliquant beaucoup de duplication de code, etc., ce qui le rend irréalisable (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 possède des champs spécifiques et le chargeur de configuration utilise la réflexion pour faire correspondre l'élément XML nommé fieldX au champ approprié de la classe et pour initialiser ce dernier. Dans certains cas, il peut créer une boîte de dialogue GUI simple à partir des propriétés identifiées à la volée. Sans réflexion, cela prendrait des centaines de lignes de code à travers plusieurs applications. La réflexion nous a donc aidés à mettre en place un outil simple rapidement, sans trop de bruit, et nous a permis de nous concentrer sur la partie importante (test de régression de notre application web, analyse des journaux du serveur, etc.) plutôt que sur la non-pertinence.
  • un module de notre application Web héritée était destiné à exporter/importer des données de tables DB 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, certains d'entre eux contenaient des bogues, etc. 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.

L'essentiel est, comme tout outil puissant, la réflexion peut également être utilisée pour vous 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 autrement simple en un désordre complexe et laid.

48
Péter Török

Ce n'est pas de la triche. 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 à la compilation - il est utile que le compilateur vérifie qu'une méthode est disponible au moment de la compilation. Si vous utilisez la réflexion, vous obtiendrez une erreur lors 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 à déboguer.
  • Cela provoque des bogues lors de la refactorisation - 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 refactorisation de code et vous aurez instantanément un bug, qui pourrait être assez difficile à localiser.
  • Les performances sont plus lentes - la réflexion à l'exécution va être plus lente que les appels de méthode/recherches de variables compilés statiquement. Si vous ne faites de la réflexion qu'à l'occasion, cela n'aura pas d'importance, mais cela peut devenir un goulot d'étranglement dans les cas où vous effectuez des appels via la réflexion des milliers ou des millions de fois par seconde. Une fois, j'ai eu une accélération 10x dans du code Clojure simplement en éliminant toute réflexion, alors oui, c'est un vrai problème.

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

  • Pour prototypage rapide ou code "jetable" quand c'est la solution la plus simple
  • Pour cas d'utilisation de réflexion authentique, par ex. IDE outillage qui permet à 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 la ou les méthodes est généralement suffisant pour résoudre la plupart des cas simples.

38
mikera

La réflexion n'est qu'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éfléchissants sont d'un niveau élevé de maintenabilité (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 de performance, mais je serais heureux d'opter pour un programme plus lent et plus facile à maintenir dans de nombreux, voire la plupart des cas.

11
DeadMG

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

Par exemple, j'ai écrit une application de vérification des médias qui utilise l'injection de dépendances pour déterminer le type de média (fichiers MP3 ou fichiers JPEG) à vérifier. Le Shell devait afficher une grille contenant les informations pertinentes pour chaque type, mais il n'avait aucune connaissance de quoi qu'il allait afficher. Ceci est défini dans l'assembly qui lit ce type de média.

J'ai donc dû utiliser la réflexion pour obtenir le nombre de colonnes à afficher ainsi que leurs types et 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 changer aucun autre code ou fichier de configuration.

La seule autre façon aurait été d'avoir un fichier de configuration qui devrait être mis à jour lorsque j'ai changé le type de média en cours de vérification. Cela aurait introduit un autre point d'échec pour l'application.

8
ChrisF

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

Comme cela permet à votre environnement de build d'inspecter le code et potentiellement générer les bons outils pour manipuler/init inspecter le code.

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

Une utilisation de développement pour la réflexion (IMO) est qu'elle rend l'écriture d'une bibliothèque de streaming générique très simple (tant que la description de votre classe ne change jamais (alors cela devient une solution très fragile)).

7
Martin York

La réflexion est un outil génial si vous êtes un auteur 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 fonctionner de manière transparente avec des appelants arbitraires, sans qu'ils aient à sauter à travers des cadres de génération de code, etc.

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

7
Marc Gravell

L'utilisation de la réflexion est souvent nuisible dans les langues 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é transmis puis appeler une de ses méthodes
  3. L'auteur refuse d'accepter que la réflexion n'est pas la bonne technique et accuse quiconque signale cela comme "ne sachant pas comment m'aider"

Icisont 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. À tout moment de votre code, vous devez en savoir suffisamment sur le type d'un objet pour savoir quelles fonctions pertinentes il possède 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 faisant en sorte que chaque fonction n'en sache pas (et n'en fasse pas plus) que ce qui est pertinent.

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

  1. La mauvaise entrée est introduite
  2. Votre code est mal structuré
  3. Tu n'as aucune idée de ce que tu fais.

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

Pour les langages OO, la réflexion ne devrait être nécessaire que dans la méta-activité (chargeurs de classe, injection de dépendances, etc.). Dans ces contextes, la réflexion est nécessaire car vous fournissez un service générique pour assister la manipulation/configuration de code dont vous ne savez rien pour une bonne et légitime raison. Dans presque toutes les autres situations, si vous cherchez à 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.

5
itsbruce

Une alternative, dans les cas où le domaine des classes reflétées 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 lors de l'exécution. Je le fais en utilisant FreeMarker/FMPP; il existe de nombreux autres outils parmi lesquels choisir. L'avantage est que vous vous retrouvez avec du "vrai" code qui peut facilement être débogué, etc.

Selon la situation, cela peut vous aider à créer un code beaucoup plus rapide - ou tout simplement beaucoup de code. Il évite les inconvénients de la réflexion:

  • perte de sécurité au moment de la compilation
  • bugs dus au refactoring
  • performances plus lentes

mentionné plus tôt.

Si la réflexion donne l'impression de tricher, c'est peut-être parce que vous la basez sur beaucoup de conjectures dont vous n'êtes pas sûr, et votre instinct 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 pouvez décrire toutes les bizarreries et les cas particuliers des classes du monde réel que vous pouvez rencontrer.

3
Ed Staub

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

2
Carra

Ce n'est pas de la triche, mais comme tout outil, il doit ê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 à travers le code; si c'est ce que vous devez faire, alors la réflexion est l'outil pour le travail. La réflexion concerne le méta-code: un code qui cible le code (par opposition au code ordinaire, qui cible les données).

Un exemple de bonne utilisation de la réflexion est les classes d'interface de service Web génériques: une conception typique consiste à séparer l'implémentation du protocole de la fonctionnalité de charge utile. Donc, vous avez une classe (appelons-la T) qui implémente votre charge utile, et une autre qui implémente le protocole (P). T est assez simple: pour chaque appel que vous voulez faire, écrivez simplement une méthode qui fait ce qu'elle est censée faire. P, cependant, doit mapper les appels de service Web aux appels de méthode. Rendre ce mappage générique est souhaitable, car il évite la redondance et rend P hautement réutilisable. Reflection fournit les moyens d'inspecter la classe T au moment de l'exécution et d'appeler ses méthodes en fonction des chaînes passées dans P via le protocole de service Web, sans aucune connaissance de la classe T au moment de la compilation . En utilisant la règle 'code about code', on peut argumenter que la classe P a le code dans la classe T comme partie de ses données.

Toutefois.

La réflexion vous donne également des outils pour contourner les restrictions du système de types du langage - théoriquement, vous pouvez passer tous les paramètres en tant que type object, et appeler leurs méthodes via des réflexions. Voilà, le langage qui est censé appliquer une forte discipline de typage statique se comporte maintenant comme un langage typé dynamiquement avec liaison tardive, mais la syntaxe est beaucoup plus élaborée. Chaque exemple d'un tel modèle que j'ai vu jusqu'à présent a été un hack sale, et invariablement, une solution dans le système de type de la langue aurait été possible, et elle aurait été plus sûre, plus élégante et plus efficace à tous égards .

Il existe quelques exceptions, telles que les contrôles GUI qui peuvent être liés aux données à divers types de sources de données non liées; exiger que vos données implémentent une certaine interface juste pour que vous puissiez les lier aux données n'est pas réaliste, et le programmeur n'implémente pas non plus d'adaptateur pour chaque type de source de données. Dans ce cas, l'utilisation de la réflexion pour détecter le type de source de données et l'ajustement de la liaison de données est un choix plus utile.

2
tdammers

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

1
Tangurena

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 largement utilisé dans la plupart des frameworks MVC. C'est une composante majeure des ORM. Il y a de fortes chances que vous utilisiez déjà tous les jours des composants construits avec.

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

1
Chris McCall

La réflexion peut réaliser des choses qui ne peuvent tout simplement pas se faire pratiquement autrement.

Par exemple, réfléchissez à l'optimisation de 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 au 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 comparables à l'extraction de ce test, sans répéter la boucle une douzaine de fois (et donc sacrifier la maintenabilité). Générez et compilez simplement la boucle dont vous avez besoin à la volée.

J'ai en fait cette optimisation , même si c'était un peu plus compliqué d'une situation, et les résultats étaient incroyables. Un ordre de grandeur d'amélioration des performances et moins de lignes de code.

(Remarque: j'ai d'abord essayé l'équivalent de passer dans un Func au lieu d'un char, et c'était légèrement mieux, mais pas presque la réflexion 10x obtenue.)

0
Craig Gidney