Ich schreibe ein Programm in Java, in dem ich irgendwann ein Passwort für meinen Keystore laden muss. Aus Spaß habe ich versucht, mein Passwort in Java so kurz wie möglich zu halten:
//Some code
....
KeyManagerFactory keyManager = KeyManagerFactory.getInstance("SunX509");
Keystore keyStore = KeyStore.getInstance("JKS");
{
char[] password = getPassword();
keyStore.load(new FileInputStream(keyStoreLocation), password);
keyManager.init(keyStore, password);
}
...
//Some more code
Nun, ich weiß in diesem Fall, dass das ein bisschen dumm ist. Es gibt eine Reihe anderer Dinge, die ich hätte tun können, die meisten sogar besser (ich hätte überhaupt keine Variable verwenden können).
Ich war jedoch neugierig, ob es einen Fall gibt, in dem dies nicht so dumm war. Die einzige andere Sache, an die ich denken kann, ist, wenn Sie gebräuchliche Variablennamen wie count
oder wiederverwenden temp
möchten, aber gute Namenskonventionen und kurze Methoden machen es unwahrscheinlich, dass dies nützlich wäre.
Gibt es einen Fall, in dem die Verwendung von Blöcken nur zur Reduzierung des Variablenumfangs sinnvoll ist?
Antworten:
Sprechen Sie zunächst mit der zugrunde liegenden Mechanik:
In C ++ scope == lifetime werden b / c-Destruktoren beim Verlassen des Gültigkeitsbereichs aufgerufen. Ein weiterer wichtiger Unterschied in C / C ++ ist, dass wir lokale Objekte deklarieren können. In der Laufzeit für C / C ++ weist der Compiler im Allgemeinen im Voraus einen Stapelrahmen für die Methode zu, der so groß ist, wie erforderlich, anstatt bei der Eingabe für jeden Bereich (der Variablen deklariert) mehr Stapelspeicher zuzuweisen. Der Compiler komprimiert oder reduziert die Bereiche.
Der C / C ++ - Compiler kann Stapelspeicherplatz für Gebietsschemas wiederverwenden, die keine Konflikte hinsichtlich der Nutzungsdauer aufweisen (im Allgemeinen wird die Analyse des tatsächlichen Codes verwendet, um dies zu bestimmen, und nicht den Bereich, der genauer ist als den Bereich!).
Ich erwähne, dass die Syntax von C / C ++ b / c Java, dh geschweifte Klammern und Gültigkeitsbereiche, zumindest teilweise von dieser Familie abgeleitet ist. Und auch, weil C ++ in fraglichen Kommentaren auftauchte.
Im Gegensatz dazu ist es in Java nicht möglich, lokale Objekte zu haben: Alle Objekte sind Heap-Objekte, und alle Locals / Formals sind entweder Referenzvariablen oder primitive Typen (übrigens gilt das Gleiche für Statics).
Darüber hinaus werden Umfang und Lebensdauer von Java nicht genau gleichgesetzt: Das Ein- und Aussteigen aus dem Geltungsbereich ist meist ein Konzept zur Kompilierungszeit, das zu Zugänglichkeits- und Namenskonflikten führt. In Java passiert beim Verlassen des Gültigkeitsbereichs in Bezug auf die Bereinigung von Variablen nichts wirklich. Die Garbage Collection von Java bestimmt (den Endpunkt der) Lebensdauer von Objekten.
Auch der Bytecode-Mechanismus von Java (die Ausgabe des Java-Compilers) tendiert dazu, Benutzervariablen, die in begrenzten Gültigkeitsbereichen deklariert wurden, auf die oberste Ebene der Methode zu befördern, da es auf Bytecode-Ebene keine Gültigkeitsbereichsfunktion gibt (dies ähnelt der C / C ++ - Behandlung von Stapel). Bestenfalls könnte der Compiler lokale Variablen-Slots wiederverwenden, jedoch (im Gegensatz zu C / C ++) nur, wenn deren Typ derselbe ist.
(Um sicherzugehen, dass der zugrunde liegende JIT-Compiler in der Laufzeit denselben Stapelspeicherplatz für zwei unterschiedlich typisierte (und unterschiedlich geschlitzte) Locals mit ausreichender Analyse des Bytecodes wiederverwenden kann.)
In Bezug auf den Vorteil des Programmierers würde ich anderen eher zustimmen, dass eine (private) Methode ein besseres Konstrukt ist, selbst wenn sie nur einmal verwendet wird.
Trotzdem ist nichts wirklich Falsches daran, damit Namen zu dekonflizieren.
Ich habe dies in seltenen Fällen getan, wenn die Herstellung einzelner Methoden eine Belastung darstellt. Wenn
switch
ich zum Beispiel einen Interpreter mit einer großen Anweisung innerhalb einer Schleife schreibe , könnte ich (abhängig von den Faktoren) für jede Anweisung einen eigenen Block einführencase
, um die einzelnen Fälle voneinander zu trennen, anstatt für jeden Fall eine andere Methode (jeweils eine andere) zu verwenden die nur einmal aufgerufen wird).(Beachten Sie, dass die einzelnen Fälle als Code in Blöcken Zugriff auf die Anweisungen "break" und "continue" in Bezug auf die umschließende Schleife haben, wohingegen als Methoden die Rückgabe von Booleschen Werten und die Verwendung von Bedingungen durch den Aufrufer erforderlich wären, um Zugriff auf diesen Kontrollfluss zu erhalten Aussagen.)
quelle
{ int x = 42; String s="blah"; } { long y = 0; }
dielong
Variable die Slots beider Variablenx
unds
aller häufig verwendeten Compiler (javac
undecj
) wiederverwendet .Meiner Meinung nach wäre es klarer, den Block in seine eigene Methode herauszuziehen. Wenn Sie diesen Block in verlassen, würde ich hoffen, einen Kommentar zu sehen, der klarstellt, warum Sie den Block dort an erster Stelle setzen. Zu diesem Zeitpunkt fühlt es sich einfach übersichtlicher an, den Methodenaufruf zu haben,
initializeKeyManager()
der die Kennwortvariable auf den Umfang der Methode beschränkt und Ihre Absicht mit dem Codeblock anzeigt.quelle
Sie können in Rust nützlich sein, da es strenge Ausleihregeln gibt. Und im Allgemeinen in funktionalen Sprachen, in denen dieser Block einen Wert zurückgibt. Aber in Sprachen wie Java? Obwohl die Idee elegant erscheint, wird sie in der Praxis nur selten verwendet, sodass die Verwirrung, sie zu sehen, die potenzielle Klarheit der Einschränkung des Gültigkeitsbereichs der Variablen in den Schatten stellt.
Es gibt jedoch einen Fall, in dem ich das nützlich finde - in einer
switch
Aussage! Manchmal möchten Sie eine Variable in einem deklarierencase
:Dies wird jedoch fehlschlagen:
switch
Anweisungen sind seltsam - sie sind Steueranweisungen, aber aufgrund ihres Durchschlags führen sie keine Bereiche ein.Sie können also einfach Blöcke verwenden, um die Bereiche selbst zu erstellen:
quelle
Es wird allgemein als gute Praxis angesehen, die Methoden kurz zu halten. Das Reduzieren des Gültigkeitsbereichs einiger Variablen kann ein Zeichen dafür sein, dass Ihre Methode ziemlich lang ist, sodass Sie der Meinung sind, dass der Gültigkeitsbereich der Variablen reduziert werden muss. In diesem Fall lohnt es sich wahrscheinlich, eine separate Methode für diesen Teil des Codes zu erstellen.
Die Fälle, die ich problematisch finden würde, sind, wenn dieser Teil des Codes mehr als eine Handvoll Argumente verwendet. Es gibt Situationen, in denen Sie möglicherweise mit kleinen Methoden enden, für die jeweils viele Parameter erforderlich sind (oder die Sie umgehen müssen, um die Rückgabe mehrerer Werte zu simulieren). Die Lösung für dieses spezielle Problem besteht darin, eine weitere Klasse zu erstellen, die die verschiedenen Variablen, die Sie verwenden, enthält und alle Schritte implementiert, die Sie in den relativ kurzen Methoden berücksichtigen: Dies ist ein typischer Anwendungsfall für die Verwendung eines Builder- Musters.
Trotzdem können all diese Kodierungsrichtlinien manchmal lose angewendet werden. Es kommt manchmal vor, dass der Code besser lesbar ist, wenn Sie eine etwas längere Methode verwenden, anstatt ihn in kleine Untermethoden (oder Builder-Muster) aufzuteilen. In der Regel funktioniert dies bei Problemen mittlerer Größe, die recht linear sind und bei denen eine zu starke Aufteilung die Lesbarkeit beeinträchtigt (insbesondere, wenn Sie zwischen zu vielen Methoden wechseln müssen, um die Funktionsweise des Codes zu verstehen).
Es kann sinnvoll sein, aber es ist sehr selten.
Hier ist dein Code:
Sie erstellen ein
FileInputStream
, aber Sie schließen es nie.Eine Möglichkeit, dies zu lösen, ist die Verwendung einer Try-with-Resources-Anweisung . Es umfasst den
InputStream
, und Sie können diesen Block auch verwenden, um den Umfang anderer Variablen zu verringern, wenn Sie dies wünschen (innerhalb des Grundes, da diese Zeilen zusammenhängen):(Es ist übrigens auch oft besser,
getDefaultAlgorithm()
statt zu verwendenSunX509
.)Darüber hinaus ist der Ratschlag, Codeblöcke in separate Methoden zu unterteilen, in der Regel zutreffend. Es lohnt sich selten, wenn es sich nur um 3 Zeilen handelt (wenn überhaupt, behindert das Erstellen einer separaten Methode die Lesbarkeit).
quelle
Meiner bescheidenen Meinung nach sollte dies im Allgemeinen weitgehend für Produktionscode vermieden werden, da Sie im Allgemeinen nur in sporadischen Funktionen versucht sind, die unterschiedliche Aufgaben ausführen. Ich tendiere dazu, es in einem Schrottcode zu tun, der zum Testen von Dingen verwendet wird, aber ich finde keine Versuchung, es im Produktionscode zu tun, wo ich vorher überlegt habe, was jede Funktion tun soll, da die Funktion dann natürlich eine sehr große Wirkung hat begrenzter Umfang in Bezug auf seinen lokalen Zustand.
Ich habe noch nie Beispiele für solche anonymen Blöcke gesehen (ohne Bedingungen, einen
try
Block für eine Transaktion usw.), um den Umfang einer Funktion auf sinnvolle Weise zu reduzieren, ohne die Frage zu stellen, warum dies nicht möglich war weiter in einfachere Funktionen mit reduziertem Umfang unterteilt werden, wenn es tatsächlich praktisch von einem echten SE-Standpunkt aus den anonymen Blöcken profitiert hat. Normalerweise ist es eklektischer Code, der eine Reihe von Dingen ausführt, die lose oder sehr unabhängig voneinander sind, und zu denen wir am meisten versucht sind, danach zu greifen.Wenn Sie beispielsweise versuchen, eine Variable mit dem Namen wiederzuverwenden
count
, deutet dies darauf hin, dass Sie zwei unterschiedliche Dinge zählen. Wenn der Variablenname so kurz sein sollcount
, ist es für mich sinnvoll, ihn an den Kontext der Funktion zu binden, der möglicherweise nur eine Art von Dingen zählt. Dann können Sie sofort den Namen und / oder die Dokumentation der Funktion anzeigen, sehencount
und sofort wissen, was sie im Kontext der Funktionsweise bedeutet, ohne den gesamten Code zu analysieren. Ich finde nicht oft ein gutes Argument für eine Funktion, um zwei verschiedene Dinge zu zählen, indem derselbe Variablenname in einer Weise wiederverwendet wird, die anonyme Bereiche / Blöcke im Vergleich zu den Alternativen so attraktiv macht. Das bedeutet nicht, dass alle Funktionen nur eines zählen müssen. ICH'Engineering-Nutzen für eine Funktion, die denselben Variablennamen zum Zählen von zwei oder mehr Dingen wiederverwendet und anonyme Blöcke verwendet, um den Umfang jeder einzelnen Zählung zu begrenzen. Wenn die Funktion einfach und klar ist, ist es nicht das Ende der Welt, zwei unterschiedlich benannte Zählvariablen zu haben, wobei die erste möglicherweise ein paar Zeilen mehr Sichtbarkeit aufweist, als im Idealfall erforderlich ist. Solche Funktionen sind im Allgemeinen nicht die Quelle von Fehlern, denen solche anonymen Blöcke fehlen, um den minimalen Umfang ihrer lokalen Variablen noch weiter zu reduzieren.Kein Vorschlag für überflüssige Methoden
Dies soll nicht bedeuten, dass Sie Methoden mit Nachdruck erstellen, um den Umfang zu verringern. Das ist wohl genauso schlimm oder schlimmer, und was ich vorschlage, sollte nicht mehr als die Notwendigkeit anonymer Bereiche die Notwendigkeit umständlicher privater "Helfer" -Methoden erfordern. Dabei geht es zu sehr um den Code in seiner jetzigen Form und darum, den Gültigkeitsbereich von Variablen zu verringern, als vielmehr darum, das Problem auf Schnittstellenebene so zu lösen, dass lokale Funktionszustände auf natürliche Weise ohne tiefes Verschachteln von Blöcken sauber und kurz sichtbar sind und 6+ Ebenen der Einrückung. Ich stimme Bruno zu, dass Sie die Lesbarkeit von Code behindern können, indem Sie 3 Codezeilen mit Gewalt in eine Funktion einfügen. Dabei wird jedoch davon ausgegangen, dass Sie die Funktionen, die Sie erstellen, basierend auf Ihrer vorhandenen Implementierung weiterentwickeln. anstatt die Funktionen zu entwerfen, ohne sich in Implementierungen zu verheddern. Wenn Sie es auf die letztere Weise tun, habe ich wenig Bedarf an anonymen Blöcken gefunden, die keinen anderen Zweck erfüllen, als den Gültigkeitsbereich einer Variablen innerhalb einer bestimmten Methode zu reduzieren, es sei denn, Sie versuchen eifrig, den Gültigkeitsbereich einer Variablen um nur ein paar Zeilen weniger harmlosen Codes zu reduzieren wo die exotische Einführung dieser anonymen Blöcke wohl so viel intellektuellen Aufwand beiträgt, wie sie entfernen.
Versuchen, den Mindestumfang noch weiter zu reduzieren
Wenn es sich gelohnt hat, die Gültigkeitsbereiche lokaler Variablen auf das absolute Minimum zu reduzieren, sollte eine breite Akzeptanz von Code wie folgt bestehen:
... da dies die minimale Sichtbarkeit des Zustands bewirkt, indem nicht einmal Variablen erstellt werden, die überhaupt auf sie verweisen. Ich möchte nicht als dogmatisch abschneiden, aber ich denke tatsächlich, dass die pragmatische Lösung darin besteht, anonyme Blöcke zu vermeiden, wenn dies möglich ist, genauso wie es die monströse Codezeile oben ist, und wenn sie im Produktionskontext aus der Sicht von so absolut notwendig erscheinen Aus der Perspektive der Korrektheit und der Beibehaltung von Invarianten in einer Funktion halte ich es auf jeden Fall für sinnvoll, Ihren Code in Funktionen zu gliedern und Ihre Schnittstellen neu zu gestalten. Wenn Ihre Methode 400 Zeilen lang ist und der Gültigkeitsbereich einer Variablen für mehr als 300 Codezeilen sichtbar ist, kann dies natürlich ein echtes Konstruktionsproblem darstellen, das jedoch nicht unbedingt mit anonymen Blöcken gelöst werden muss.
Nicht zuletzt ist die Verwendung anonymer Blöcke überall exotisch und nicht idiomatisch, und exotischer Code birgt das Risiko, Jahre später von anderen, wenn nicht von Ihnen selbst, gehasst zu werden.
Der praktische Nutzen der Einschränkung des Anwendungsbereichs
Der ultimative Nutzen der Reduzierung des Gültigkeitsbereichs von Variablen besteht darin, dass Sie die Zustandsverwaltung korrekt ausführen und beibehalten können und auf einfache Weise über die Funktionsweise eines bestimmten Teils einer Codebasis nachdenken können, um konzeptionelle Invarianten beizubehalten. Wenn die Verwaltung des lokalen Status einer einzelnen Funktion so komplex ist, dass Sie den Umfang mit einem anonymen Codeblock, der nicht finalisiert und fehlerfrei sein soll, nachdrücklich reduzieren müssen, ist dies wiederum ein Zeichen für mich, dass die Funktion selbst erneut überprüft werden muss . Wenn Sie Schwierigkeiten haben, über die Zustandsverwaltung von Variablen in einem lokalen Funktionsbereich nachzudenken, stellen Sie sich die Schwierigkeit vor, über die privaten Variablen nachzudenken, auf die jede Methode einer ganzen Klasse zugreifen kann. Wir können keine anonymen Blöcke verwenden, um deren Sichtbarkeit zu verringern. Für mich ist es hilfreich, mit der Annahme zu beginnen, dass Variablen tendenziell einen etwas größeren Umfang haben, als sie im Idealfall in vielen Sprachen haben müssen, vorausgesetzt, sie geraten nicht außer Kontrolle, bis Sie Schwierigkeiten haben, Invarianten zu verwalten. Es ist nicht so viel mit anonymen Blöcken zu lösen, wie ich aus pragmatischer Sicht annehme.
quelle
Ich denke, dass Motivation Grund genug ist, einen Block einzuführen, ja.
Wie Casey bemerkte, handelt es sich bei dem Block um einen "Codegeruch", der darauf hinweist, dass Sie den Block besser in eine Funktion extrahieren sollten. Aber du darfst nicht.
John Carmark schrieb 2007 ein Memo über Inline-Code . Seine abschließende Zusammenfassung
So denkt , machen Sie eine Wahl mit dem Zweck, und (optional) leave Beweise zur Beschreibung Ihrer Motivation für die Wahl , die Sie gemacht.
quelle
Meine Meinung mag umstritten sein, aber ich denke, dass es Situationen gibt, in denen ich diesen Stil verwenden würde. "Nur um den Gültigkeitsbereich der Variablen zu verringern" ist jedoch kein gültiges Argument, da Sie dies bereits tun. Und etwas zu tun, nur um es zu tun, ist kein triftiger Grund. Beachten Sie auch, dass diese Erklärung keine Bedeutung hat, wenn Ihr Team bereits entschieden hat, ob diese Art von Syntax konventionell ist oder nicht.
Ich bin in erster Linie ein C # -Programmierer, aber ich habe gehört, dass in Java ein Kennwort zurückgegeben
char[]
wird, um die Zeit zu verkürzen, die das Kennwort im Speicher verbleibt. In diesem Beispiel ist dies nicht hilfreich, da der Garbage Collector das Array ab dem Zeitpunkt abrufen darf, an dem es nicht verwendet wird. Daher spielt es keine Rolle, ob das Kennwort den Gültigkeitsbereich verlässt. Ich streite nicht darüber, ob es sinnvoll ist, das Array zu löschen, nachdem Sie damit fertig sind, aber in diesem Fall ist es sinnvoll, die Variable zu definieren, wenn Sie dies möchten:Dies ist der Anweisung try-with-resources sehr ähnlich , da sie die Ressource durchsucht und eine Finalisierung vornimmt, nachdem Sie fertig sind. Bitte beachten Sie auch hier, dass ich nicht für den Umgang mit Passwörtern auf diese Weise plädiere, nur dass dieser Stil angemessen ist, wenn Sie sich dazu entschließen.
Der Grund dafür ist, dass die Variable nicht mehr gültig ist. Sie haben es erstellt, verwendet und seinen Status ungültig gemacht, damit er keine aussagekräftigen Informationen enthält. Es macht keinen Sinn, die Variable nach diesem Block zu verwenden, daher ist es sinnvoll, sie zu erfassen.
Ein anderes Beispiel, an das ich denken kann, ist, wenn Sie zwei Variablen haben, die einen ähnlichen Namen und eine ähnliche Bedeutung haben, aber mit einer und dann mit einer anderen arbeiten und diese getrennt halten möchten. Ich habe diesen Code in C # geschrieben:
Sie können argumentieren, dass ich einfach
ILGenerator il;
oben in der Methode deklarieren kann , aber ich möchte die Variable auch nicht für verschiedene Objekte wiederverwenden (irgendwie funktionaler Ansatz). In diesem Fall erleichtern die Blöcke die syntaktische und visuelle Trennung der ausgeführten Jobs. Es sagt auch, dass ich nach dem Block fertig binil
und nichts mehr darauf zugreifen soll.Ein Argument gegen dieses Beispiel ist die Verwendung von Methoden. Vielleicht ja, aber in diesem Fall ist der Code nicht so lang. Wenn Sie ihn in verschiedene Methoden unterteilen, müssen Sie auch alle Variablen weitergeben, die im Code verwendet werden.
quelle