Warum ist es eine schlechte Idee, einen generischen Setter und Getter mit Reflektion zu erstellen?

49

Vor einiger Zeit habe ich diese Antwort auf die Frage geschrieben, wie man vermeidet, für jede veränderbare Variable einen Getter und Setter zu haben. Zu der Zeit hatte ich nur ein schwer zu verbalisierendes Gefühl, dass dies eine schlechte Idee ist, aber OP fragte ausdrücklich, wie es geht. Ich habe hier nach dem Grund gesucht, warum dies ein Problem sein könnte, und diese Frage gefunden , deren Antworten es als gegeben zu betrachten scheinen, dass die Verwendung von Reflexion, es sei denn, dies ist absolut notwendig, eine schlechte Praxis.

Kann also jemand verbalisieren, warum es selbstverständlich ist, dass Reflexionen vermieden werden sollten, insbesondere im Fall des generischen Getters und Setters?

HAEM
quelle
12
Wenn Sie nur das Boilerplate reduzieren möchten, werden sowohl Lombok als auch Groovy geräuschlos, aber sicher Getter und Setter erzeugen.
chrylis -on strike-
2
Wie würden Sie diese Getter und Setter in einer statischen Sprache wie Java konsumieren? Es scheint mir ein Nichtstarter zu sein.
Esben Skov Pedersen
@EsbenSkovPedersen Du würdest sie ähnlich wie a Map's getund konsumieren put, was eine ziemlich klobige Art ist, Dinge zu tun.
HAEM
2
IIRC, Lombok synthetisiert Getters / Setter zur Kompilierungszeit, wie durch die entsprechende Annotation vorgegeben, so dass sie: keine Performanceeinbußen durch Reflektion erleiden, statisch analysiert / inliniert werden können, überarbeitet werden können (IntelliJ hat ein Plugin für Lombok)
Alexander

Antworten:

117

Nachteile der Reflexion im Allgemeinen

Reflexion ist schwerer zu verstehen als linearer Code.

Meiner Erfahrung nach ist Reflektion in Java ein Feature auf Expertenebene. Ich würde argumentieren, dass die meisten Programmierer Reflection niemals aktiv verwenden (dh Bibliotheken, die Reflection verwenden, werden nicht gezählt). Für diese Programmierer ist die Verwendung von Code daher schwieriger zu verstehen.

Der Reflektionscode ist für die statische Analyse nicht zugänglich

Angenommen, ich habe einen Getter getFooin meiner Klasse und möchte ihn in umbenennen getBar. Wenn ich keine Reflektion verwende, kann ich einfach die Codebasis durchsuchen getFoound jeden Ort finden, der den Getter verwendet, damit ich ihn aktualisieren kann, und selbst wenn ich einen vermisse, wird der Compiler sich beschweren.

Aber wenn der Ort, an dem der Getter verwendet wird, so ähnlich ist wie callGetter("Foo")und callGetterist getClass().getMethod("get"+name).invoke(this), wird er von der obigen Methode nicht gefunden und der Compiler wird sich nicht beschweren. Nur wenn der Code tatsächlich ausgeführt wird, erhalten Sie eine NoSuchMethodException. Und stellen Sie sich den Schmerz vor, den Sie verspüren, wenn diese Ausnahme (die nachverfolgt wird) verschluckt wird, callGetterweil "sie nur mit hartcodierten Zeichenfolgen verwendet wird, sie kann eigentlich nicht passieren". (Niemand würde das tun, könnte jemand argumentieren? Außer dass das OP genau das in seiner SO-Antwort getan hat . Wenn das Feld umbenannt wird, würden Benutzer des generischen Setters es nie bemerken, außer dass der äußerst obskure Fehler des Setters im Stillen nichts tut. Benutzer des Getters können, wenn sie Glück haben, die Konsolenausgabe der ignorierten Ausnahme bemerken.)

Der Reflektionscode wird vom Compiler nicht typgeprüft

Dies ist im Grunde ein großer Unterpunkt des oben Gesagten. Reflexionscode dreht sich alles um Object. Typen werden zur Laufzeit geprüft. Fehler werden durch Unit-Tests entdeckt, aber nur, wenn Sie Abdeckung haben. ("Es ist nur ein Getter, ich muss es nicht testen.") Grundsätzlich verlieren Sie den Vorteil, den Sie durch die Verwendung von Java gegenüber Python gewonnen haben.

Der Reflektionscode ist für die Optimierung nicht verfügbar

Vielleicht nicht in der Theorie, aber in der Praxis werden Sie keine JVM finden, die einen Inline-Cache für Inlinespeicher erstellt Method.invoke. Für solche Optimierungen stehen normale Methodenaufrufe zur Verfügung. Das macht sie viel schneller.

Der Reflektionscode ist im Allgemeinen nur langsam

Die für den Reflection-Code erforderliche dynamische Methodensuche und Typprüfung ist langsamer als normale Methodenaufrufe. Wenn Sie diesen billigen Einzeilen-Getter in ein Reflexionstier verwandeln, könnten Sie (ich habe dies nicht gemessen) mehrere Größenordnungen der Verlangsamung betrachten.

Nachteil der generischen Getter / Setter speziell

Das ist nur eine schlechte Idee, da Ihre Klasse jetzt keine Kapselung mehr hat. Jedes Feld ist zugänglich. Sie können sie auch alle öffentlich machen.

Sebastian Redl
quelle
8
Sie haben alle wichtigen Punkte erreicht. So ziemlich eine perfekte Antwort. Ich könnte darüber streiten, dass Getter und Setter geringfügig besser sind als öffentliche Felder, aber der größere Punkt ist richtig.
JimmyJames
2
Erwähnenswert ist auch, dass Getter / Setter auch von einer IDE leicht generiert werden können.
stiemannkj1
26
+1 Das Debuggen von Reflexionslogik in einem Projekt in Produktionsgröße sollte von den Vereinten Nationen als Folter anerkannt und illegalisiert werden.
Errantlinguist
4
"Für diese Programmierer ist es schwerer zu verstehen." - Für diese Programmierer ist es sehr schwer zu verstehen, aber es ist für alle schwerer zu verstehen, egal wie kompetent sie sind.
BartoszKP
4
"Der Reflektionscode ist für statische Analysen nicht zugänglich" - Dies. So wichtig für das Refactoring. Und da statische Analysewerkzeuge immer leistungsfähiger werden (z. B. die Codesuche im neuesten Team Foundation Server), wird das Schreiben von Code, der sie ermöglicht, noch wertvoller.
Dan J
11

Weil Pass-Through-Getter / Setter ein Greuel sind, der keinen Wert liefert. Sie bieten keine Kapselung, da Benutzer über die Eigenschaften auf die tatsächlichen Werte zugreifen können. Sie sind YAGNI, weil Sie die Implementierung Ihres Feldspeichers selten oder nie ändern werden.

Und weil das viel weniger performant ist. Zumindest in C # wird ein Getter / Setter direkt in eine einzelne CIL-Anweisung eingebunden. In Ihrer Antwort ersetzen Sie dies durch 3 Funktionsaufrufe, die wiederum Metadaten laden müssen, um zu reflektieren. Ich habe im Allgemeinen 10x als Faustregel für die Reflektionskosten verwendet, aber als ich nach Daten suchte, enthielt eine der ersten Antworten diese Antwort, die näher an 1000x geparkt wurde.

(Und es erschwert das Debuggen Ihres Codes und beeinträchtigt die Benutzerfreundlichkeit, da es mehr Möglichkeiten gibt, ihn zu missbrauchen, und es beeinträchtigt die Wartbarkeit, da Sie jetzt eher magische Zeichenfolgen als richtige Bezeichner verwenden ...)

Telastyn
quelle
2
Beachten Sie, dass die Frage mit Java getaggt ist, was so wunderbaren Mist wie das Beans-Konzept hat. Eine Bean hat keine öffentlichen Felder, kann jedoch Felder über getX()und setX()Methoden verfügbar machen, die über Reflection gefunden werden können. Beans sollen nicht unbedingt ihre Werte verkapseln, sie könnten nur DTOs sein. In Java sind Getter oft ein notwendiger Schmerz. C # -Eigenschaften sind in jeder Hinsicht viel, viel schöner.
amon
11
Bei C # -Eigenschaften handelt es sich jedoch um verkleidete Getter / Setter. Aber ja, das Beans-Konzept ist eine Katastrophe.
Sebastian Redl
6
@amon Richtig, C # hat eine schreckliche Idee und ist einfacher zu implementieren.
JimmyJames
5
@ JimmyJames Es ist keine schreckliche Idee, wirklich; Sie können die ABI-Kompatibilität trotz Codeänderungen beibehalten. Ein Problem, das viele nicht haben, nehme ich an.
Casey
3
@Casey Der Umfang davon geht über einen Kommentar hinaus, aber das Erstellen einer Schnittstelle aus den internen Details der Implementierung ist rückwärts. Es führt zu einer prozeduralen Gestaltung. Es gibt Zeiten, in denen eine getX-Methode die richtige Antwort ist, aber sie kommt in gut entworfenem Code nur selten vor. Das Problem mit Gettern und Setzern in Java ist nicht der Code, der sie umgibt, sondern dass sie Teil eines schrecklichen Designansatzes sind. Das einfacher zu machen, ist wie es einfacher zu machen, sich selbst ins Gesicht zu schlagen.
JimmyJames
8

Zusätzlich zu den bereits vorgebrachten Argumenten.

Es bietet nicht die erwartete Schnittstelle.

Benutzer erwarten, foo.getBar () und foo.setBar (value) aufzurufen, nicht foo.get ("bar") und foo.set ("bar", value)

Um die Schnittstelle bereitzustellen, die Benutzer erwarten, müssen Sie für jede Eigenschaft einen separaten Getter / Setter schreiben. Sobald Sie dies getan haben, macht es keinen Sinn, Reflexion zu verwenden.

Peter Green
quelle
Mir scheint, dass dieser Grund in anderen Antworten wichtiger ist als alle anderen.
Esben Skov Pedersen
-4

Natürlich ist es keine schlechte Idee, es ist einfach so, als wäre in einer normalen Java-Welt eine schlechte Idee (wenn man nur einen Getter oder Setter haben möchte). Zum Beispiel verwenden einige Frameworks (spring mvc) oder Librares es bereits (jstl) auf diese Weise, einige json o xml-Parser verwenden es, wenn Sie ein DSL verwenden möchten, werden Sie es verwenden. Dann kommt es auf Ihr Anwendungsfall-Szenario an.

Nichts ist richtig oder falsch als Tatsache.

als eine Tatsache
quelle