it-swarm.com.de

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

Vor einiger Zeit schrieb ich diese Antwort auf eine Frage, wie man vermeiden kann, für jede veränderliche Variable einen Getter und Setter zu haben. Zu der Zeit hatte ich nur ein schwer zu verbalisierendes Bauchgefühl, dass dies eine schlechte Idee ist, aber OP fragte ausdrücklich, wie es geht. Ich suchte hier, warum dies ein Problem sein könnte, und fand diese Frage , dessen Antworten es als gegeben voraussetzen, dass die Verwendung von Reflexion, sofern dies nicht unbedingt erforderlich ist, eine schlechte Praxis ist.

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

50
HAEM

Nachteile der Reflexion im Allgemeinen

Reflexion ist schwerer zu verstehen als geradliniger Code.

Nach meiner Erfahrung ist Reflexion in Java eine Funktion auf Expertenebene. Ich würde argumentieren, dass die meisten Programmierer Reflexion niemals aktiv nutzen (d. H. Das Konsumieren von Bibliotheken, die Reflexion verwenden, zählt nicht). Das macht es für diese Programmierer schwieriger, Code zu verwenden.

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

Angenommen, ich habe einen Getter getFoo in meiner Klasse und möchte ihn in getBar umbenennen. Wenn ich keine Reflexion verwende, kann ich einfach die Codebasis nach getFoo durchsuchen und finde jeden Ort, an dem der Getter verwendet wird, damit ich ihn aktualisieren kann. Selbst wenn ich einen vermisse, beschwert sich der Compiler.

Aber wenn der Ort, an dem der Getter verwendet wird, so etwas wie callGetter("Foo") und callGettergetClass().getMethod("get"+name).invoke(this) ist, wird die obige Methode ihn nicht finden und der Compiler nicht beschweren. Nur wenn der Code tatsächlich ausgeführt wird, erhalten Sie ein NoSuchMethodException. Und stellen Sie sich den Schmerz vor, in dem Sie sich befinden, wenn diese Ausnahme (die verfolgt wird) von callGetter verschluckt wird, weil "sie nur mit fest codierten Zeichenfolgen verwendet wird und nicht tatsächlich auftreten kann". (Niemand würde das tun, könnte jemand argumentieren? Außer, dass das OP genau das in seiner SO Antwort. Hat. Wenn das Feld umbenannt wird, würden Benutzer des generischen Setters Niemals bemerken, außer dem extrem undurchsichtigen Fehler, dass der Setter stillschweigend nichts tut. Benutzer des Getters können, wenn sie Glück haben, die Konsolenausgabe der ignorierten Ausnahme bemerken.)

Der Reflexionscode wird vom Compiler nicht typgeprüft

Dies ist im Grunde ein großer Unterpunkt des oben Gesagten. Im Reflexionscode dreht sich alles um Object. Typen werden zur Laufzeit überprüft. Fehler werden durch Komponententests entdeckt, jedoch nur, wenn Sie über eine Abdeckung verfügen. ("Es ist nur ein Getter, ich muss es nicht testen.") Grundsätzlich verlieren Sie den Vorteil, wenn Sie Java over Python haben Sie in der gewonnen) erster Platz.

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

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

Der Reflexionscode ist im Allgemeinen nur langsam

Die für den Reflektionscode erforderliche dynamische Methodensuche und Typprüfung ist langsamer als bei normalen Methodenaufrufen. Wenn Sie diesen billigen einzeiligen Getter in ein Reflexionstier verwandeln, könnten Sie (ich habe dies nicht gemessen) mehrere Größenordnungen der Verlangsamung betrachten.

Nachteil von generischen Getter/Setter speziell

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

118
Sebastian Redl

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

Und , weil dies viel weniger performant ist. Zumindest in C # wird ein Getter/Setter direkt zu einem einzelnen CIL-Befehl inline. In Ihrer Antwort ersetzen Sie dies durch 3 Funktionsaufrufe, die wiederum Metadaten laden müssen, um darüber nachzudenken. Ich habe im Allgemeinen 10x als Faustregel für die Reflexionskosten verwendet, aber als ich nach Daten suchte, enthielt eine der ersten Antworten diese Antwort , was es näher an 1000x brachte.

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

11
Telastyn

Zusätzlich zu den bereits vorgebrachten Argumenten.

Es bietet nicht die erwartete Schnittstelle.

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

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.

8
Peter Green