web-dev-qa-db-fra.com

Pourquoi est-ce une mauvaise idée de créer un setter et getter générique avec réflexion?

Il y a quelque temps, j'ai écrit cette réponse à une question sur la façon d'éviter d'avoir un getter et un setter pour chaque variable mutable. À l'époque, je n'avais qu'une intuition difficile à verbaliser que c'était une mauvaise idée, mais OP demandait explicitement comment le faire. J'ai cherché ici pourquoi cela pourrait être un problème, et j'ai trouvé cette question , dont les réponses semblent prendre pour acquis que l'utilisation de la réflexion, sauf si absolument nécessaire, est une mauvaise pratique.

Alors, quelqu'un peut-il expliquer pourquoi il est certain que la réflexion doit être évitée, en particulier dans le cas du getter et du setter génériques?

50
HAEM

Inconvénients de la réflexion en général

La réflexion est plus difficile à comprendre que le code linéaire.

D'après mon expérience, la réflexion est une fonctionnalité de "niveau expert" en Java. Je dirais que la plupart des programmeurs n'utilisent jamais la réflexion activement (c'est-à-dire que la consommation de bibliothèques qui utilisent la réflexion ne compte pas). Cela rend le code plus difficile à comprendre pour ces programmeurs.

Le code de réflexion est inaccessible à l'analyse statique

Supposons que j'ai un getter getFoo dans ma classe et que je veuille le renommer getBar. Si je n'utilise aucune réflexion, je peux simplement rechercher la base de code pour getFoo et trouverai chaque endroit qui utilise le getter afin que je puisse le mettre à jour, et même si j'en manque un, le compilateur se plaindra.

Mais si l'endroit qui utilise le getter est quelque chose comme callGetter("Foo") et callGetter fait getClass().getMethod("get"+name).invoke(this), alors la méthode ci-dessus ne le trouvera pas, et le compilateur ne le fera pas se plaindre. Ce n'est que lorsque le code est réellement exécuté que vous obtiendrez un NoSuchMethodException. Et imaginez la douleur que vous ressentez si cette exception (qui est suivie) est avalée par callGetter car "elle n'est utilisée qu'avec des chaînes codées en dur, elle ne peut pas réellement se produire". (Personne ne ferait cela, quelqu'un pourrait argumenter? Sauf que l'OP a exactement cela dans sa réponse SO. Si le champ est renommé, les utilisateurs du setter générique ne remarquez jamais, à l'exception du bug extrêmement obscur du setter qui ne fait rien en silence. Les utilisateurs du getter peuvent, s'ils sont chanceux, remarquer la sortie console de l'exception ignorée.)

Le code de réflexion n'est pas vérifié par le compilateur

Il s'agit essentiellement d'un grand sous-point de ce qui précède. Le code de réflexion concerne tout Object. Les types sont vérifiés lors de l'exécution. Les erreurs sont découvertes par des tests unitaires, mais uniquement si vous avez une couverture. ("C'est juste un getter, je n'ai pas besoin de le tester.") Fondamentalement, vous perdez l'avantage en utilisant Java sur Python vous a gagné dans le première place.

Le code de réflexion n'est pas disponible pour l'optimisation

Peut-être pas en théorie, mais en pratique, vous ne trouverez pas de machine virtuelle Java qui inline ou crée un cache en ligne pour Method.invoke. Des appels de méthode normaux sont disponibles pour de telles optimisations. Cela les rend beaucoup plus rapides.

Le code de réflexion est généralement lent en général

La recherche de méthode dynamique et la vérification de type nécessaires au code de réflexion sont plus lentes que les appels de méthode normaux. Si vous transformez ce getter à une ligne bon marché en une bête de réflexion, vous pourriez (je n'ai pas mesuré cela) examiner plusieurs ordres de grandeur de ralentissement.

Inconvénient du getter/setter générique en particulier

C'est juste une mauvaise idée, car votre classe n'a plus d'encapsulation. Chaque champ dont il dispose est accessible. Autant les rendre tous publics.

118
Sebastian Redl

Parce que les getter/setters pass-through sont une abomination qui n'apporte aucune valeur. Ils ne fournissent aucune encapsulation puisque les utilisateurs peuvent obtenir les valeurs réelles via les propriétés. Ils sont YAGNI parce que vous allez rarement, voire jamais, changer la mise en œuvre de votre stockage sur le terrain.

Et parce que c'est beaucoup moins performant. En C # au moins, un getter/setter s'alignera directement sur une seule instruction CIL. Dans votre réponse, vous remplacez cela par 3 appels de fonction, qui à leur tour doivent charger des métadonnées pour y réfléchir. J'ai généralement utilisé 10x comme règle de base pour les coûts de réflexion, mais lorsque j'ai recherché des données, l'une des premières réponses comprenait cette réponse qui la rapprochait de 1000x.

(Et cela rend votre code plus difficile à déboguer, et il nuit à la convivialité car il y a plus de façons de l'utiliser à mauvais escient, et il nuit à la maintenabilité parce que vous utilisez maintenant des chaînes magiques plutôt que des identifiants appropriés ...)

11
Telastyn

En plus des arguments déjà présentés.

Il ne fournit pas l'interface attendue.

Les utilisateurs s'attendent à appeler foo.getBar () et foo.setBar (valeur), pas foo.get ("bar") et foo.set ("bar", valeur)

Pour fournir l'interface que les utilisateurs attendent, vous devez écrire un getter/setter séparé pour chaque propriété. Une fois que vous avez fait cela, il est inutile d'utiliser la réflexion.

8
Peter Green