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

49

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, j’avais le sentiment difficile que j’entendais dire que c’était une mauvaise idée, mais OP demandait explicitement comment le faire. J'ai cherché ici pourquoi cela pouvait ê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 à moins que cela ne soit absolument nécessaire est une mauvaise pratique.

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

HAEM
la source
12
Si vous souhaitez simplement réduire le nombre de points chauds, Lombok et Groovy généreront des getters et des setters en silence mais en toute sécurité.
chrylis -on strike-
2
Comment consommeriez-vous même ces accesseurs et ces setters dans un langage statique comme java? Cela semble être un non-starter pour moi.
Esben Skov Pedersen
@EsbenSkovPedersen Vous souhaitez les consommer beaucoup comme un Mapde getet put, ce qui est une façon assez maladroite de faire les choses.
HAEM
2
IIRC, Lombok synthétise les getters / setters au moment de la compilation, comme indiqué par l'annotation appropriée, de sorte qu'ils: n'engendrent pas de pénalité de performance, puissent être analysés / alignés statiquement, peuvent être refactorisés (IntelliJ dispose d'un plugin pour Lombok)
Alexander

Réponses:

117

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

La réflexion est plus difficile à comprendre que le code en ligne droite.

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 de réflexion de manière active (c’est-à-dire que les bibliothèques consommatrices de réflexion ne comptent 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 getFoodans ma classe et que je veuille le renommer getBar. Si je n'utilise pas de réflexion, je peux simplement chercher dans la base de code getFooet trouver 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 le lieu qui utilise le getter est quelque chose comme callGetter("Foo")et callGetterfait getClass().getMethod("get"+name).invoke(this), la méthode ci - dessus ne trouvera pas, et le compilateur ne se plaindra pas. Vous obtiendrez un NoSuchMethodException. Et imaginez la douleur que vous éprouvez si cette exception (qui est suivie) est avalée callGettercar "elle est uniquement utilisée avec des chaînes codées en dur, cela ne peut pas réellement arriver". (Personne ne ferait cela, pourrait-on arguer? Sauf que l'OP l'a fait exactement dans sa réponse SO. Si le champ est renommé, les utilisateurs du configurateur générique ne le remarqueront jamais, à l'exception du bogue extrêmement obscur du poseur qui ne fait rien en silence. Les utilisateurs du getter peuvent, s’ils ont de la chance, remarquer la sortie console de l’exception ignorée.)

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

Ceci est fondamentalement un gros sous-point de ce qui précède. Le code de réflexion est tout au sujet Object. Les types sont vérifiés à l'exécution. Les erreurs sont découvertes par des tests unitaires, mais uniquement si vous êtes couvert. ("C'est juste un getter, je n'ai pas besoin de le tester.") En gros, vous perdez l'avantage en utilisant Java par rapport à Python.

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 qui installe ou crée un cache en ligne Method.invoke. Des appels de méthode normaux sont disponibles pour de telles optimisations. Cela les rend beaucoup plus rapide.

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

La recherche de méthode dynamique et la vérification de type nécessaires pour le code de réflexion sont plus lentes que les appels de méthode normaux. Si vous transformez ce getter bon marché sur une ligne en une bête de réflexion, vous risquez (je ne l’ai pas encore mesuré) d’observer un ralentissement de plusieurs ordres de grandeur.

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. Vous pourriez aussi bien les rendre tous publics.

Sebastian Redl
la source
8
Vous touchez tous les points principaux. Assez bien une réponse parfaite. Je pourrais chicaner que les Getters et les Setter sont légèrement meilleurs que les terrains publics, mais le gros problème est correct.
JimmyJames
2
Il est également intéressant de noter que les getters / setters peuvent facilement être générés par un IDE.
Stiemannkj1
26
+1 La logique de réflexion du débogage dans un projet de taille productive doit être reconnue comme une torture par l'ONU et illégalisée.
errantlinguist
4
"plus difficile à comprendre pour ces programmeurs." - Pour ces programmeurs, c'est très difficile à comprendre, mais c'est plus difficile à comprendre pour tout le monde, peu importe sa compétence.
BartoszKP
4
"Le code de réflexion est inaccessible à l'analyse statique" - Ceci. Tellement important pour permettre la refactorisation. Et à mesure que les outils d’analyse statique deviennent plus puissants (recherche de code dans la dernière version de Team Foundation Server, par exemple), la rédaction de code qui leur permet de gagner encore plus de valeur.
Dan J
11

Parce que les getter / setters pass-through sont une abomination qui ne fournit aucune valeur. Ils ne fournissent aucune encapsulation car 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'aligne directement sur une seule instruction CIL. Dans votre réponse, vous remplacez cela par 3 appels de fonction, qui doivent à leur tour charger des métadonnées pour les refléter. J'ai généralement utilisé 10x comme règle de base pour les coûts de réflexion, mais lorsque j'ai cherché des données, l'une des premières réponses incluait cette réponse qui lui a permis de se rapprocher de 1000x.

(Et cela rend votre code plus difficile à déboguer, et cela nuit à la convivialité, car il existe plus de moyens de le mal utiliser, et à la maintenabilité, car vous utilisez maintenant des chaînes magiques plutôt que des identifiants appropriés ...)

Telastyn
la source
2
Notez que la question est étiquetée Java, qui a une merde aussi délicieuse que le concept Beans. Un haricot n'a pas de champ public, mais il peut exposer les champs via getX()et setX()méthodes qui peuvent être trouvés par réflexion. Les haricots ne sont pas nécessairement censés encapsuler leurs valeurs, ils peuvent simplement être des DTO. Ainsi, à Java, les getters sont souvent une douleur nécessaire. Les propriétés C # sont beaucoup, beaucoup plus belles à tous égards.
amon
11
Les propriétés C # sont des accesseurs / régleurs habillés. Mais oui, le concept de haricots est un désastre.
Sebastian Redl
6
@amon Oui, C # a pris une idée terrible et est plus facile à mettre en œuvre.
JimmyJames
5
@ JimmyJames Ce n'est pas une idée terrible, vraiment; vous pouvez maintenir la compatibilité ABI malgré les modifications de code. Un problème que beaucoup n'ont pas, je suppose.
Casey
3
@ Casey La portée de ceci va au-delà d'un commentaire, mais la construction d'une interface à partir des détails internes de la mise en œuvre va en arrière. Cela conduit à un design procédural. Parfois, avoir la méthode getX est la bonne réponse, mais c'est assez rare dans un code bien conçu. Le problème des accesseurs et des passeurs en Java n’est pas le code standard qui les entoure, c’est qu’ils font partie d’une approche de conception épouvantable. Rendre cela plus facile revient à faciliter la frappe au visage.
JimmyJames
8

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), et non foo.get ("bar") et foo.set ("bar", valeur)

Pour fournir l’interface attendue par les utilisateurs, 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.

Peter Green
la source
Il me semble que cette raison est plus importante que toutes les autres dans les autres réponses.
Esben Skov Pedersen
-4

Bien sûr, ce n’est pas une mauvaise idée, c’est juste que dans un monde Java normal, c’est une mauvaise idée (quand vous ne voulez qu’un getter ou un setter). Par exemple, certains frameworks (spring mvc) ou certaines bibliothèques l'utilisent déjà (jstl) de cette façon, certains analyseurs json ou xml l'utilisent, si vous voulez utiliser un DSL, vous allez l'utiliser. Ensuite, cela dépend de votre scénario d'utilisation.

Rien n'est vrai ou faux comme un fait.

comme un fait
la source