Ist es sinnvoll, Blöcke zu erstellen, um den Gültigkeitsbereich einer Variablen zu verringern?


38

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 countoder wiederverwenden tempmö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?


13
@gnat .... was? Ich weiß nicht einmal, wie ich meine Frage so bearbeiten soll, dass sie dem betrogenen Ziel weniger ähnlich ist. Welcher Teil davon hat Sie dazu veranlasst zu glauben, dass diese Fragen gleich sind?
Lord Farquaad

22
Die übliche Antwort darauf ist, dass Ihr anonymer Block stattdessen eine Methode mit einem signifikanten Namen sein sollte, die Ihnen automatische Dokumentation und den von Ihnen gewünschten reduzierten Umfang bietet . Auf den ersten Blick kann ich mir keine Situation vorstellen, in der das Extrahieren einer Methode nicht besser ist als das Einfügen eines nackten Blocks.
Kilian Foth

4
Es gibt ein paar ähnliche Fragen, die dies für C ++ stellen: Ist es eine schlechte Praxis, Codeblöcke zu erstellen? und Strukturcode mit geschweiften Klammern . Vielleicht enthält eine davon eine Antwort auf diese Frage, obwohl C ++ und Java in dieser Hinsicht sehr unterschiedlich sind.
amon


4
@gnat Warum ist diese Frage überhaupt offen? Es ist eines der umfassendsten Dinge, die ich je gesehen habe. Ist es ein Versuch einer kanonischen Frage?
jpmc26

Antworten:


34

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 switchich 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ühren case, 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.)


Sehr interessante Antwort (+1)! Eine Ergänzung zum C ++ Teil. Wenn password eine Zeichenfolge in einem Block ist, weist das Zeichenfolgenobjekt dem Teil mit variabler Größe Speicherplatz im freien Speicher zu. Beim Verlassen des Blocks wird der String zerstört, was zur Freigabe des variablen Teils führt. Da es sich jedoch nicht auf dem Stapel befindet, gibt es keine Garantie dafür, dass es bald überschrieben wird. Verwalte die Vertraulichkeit besser, indem du jedes Zeichen überschreibst, bevor die Zeichenfolge zerstört wird ;-)
Christophe,

1
@ErikEidt Ich habe versucht, die Aufmerksamkeit auf das Problem zu lenken, von "C / C ++" zu sprechen, als ob es tatsächlich ein "Ding" wäre, aber es ist nicht so: "In der Laufzeit für C / C ++ wird der Compiler im Allgemeinen einen Stack-Frame zuweisen für die Methode , die so groß wie nötig ist ", aber C hat überhaupt keine Methoden. Es hat Funktionen. Diese unterscheiden sich insbesondere in C ++.
Donnerstag,

7
" Im Gegensatz dazu ist es in Java nicht möglich, lokale Objekte zu haben: Alle Objekte sind Heap-Objekte, und alle lokalen / formalen Objekte sind entweder Referenzvariablen oder primitive Typen (übrigens gilt das auch für Statik). " - Beachten Sie, dass dies der Fall ist Nicht ganz richtig, insbesondere nicht für lokale Methodenvariablen. Die Escape-Analyse kann dem Stapel Objekte zuweisen.
Boris die Spinne

1
Bestenfalls könnte der Compiler lokale Variablen-Slots wiederverwenden, aber (anders als in C / C ++) nur, wenn ihr Typ derselbe ist. Ist einfach falsch. Auf Bytecode-Ebene können lokale Variablen nach Belieben zugewiesen und neu zugewiesen werden. Die einzige Einschränkung besteht darin, dass die spätere Verwendung mit dem Typ des zuletzt zugewiesenen Elements kompatibel ist. Die Wiederverwendung lokaler Variablen-Slots ist die Norm, z. B. wenn { int x = 42; String s="blah"; } { long y = 0; }die longVariable die Slots beider Variablen xund saller häufig verwendeten Compiler ( javacund ecj) wiederverwendet .
Holger

1
@Boris the Spider: Das ist eine oft wiederholte umgangssprachliche Aussage, die nicht wirklich mit dem übereinstimmt, was vor sich geht. Die Escape-Analyse kann die Skalierung aktivieren. Hierbei handelt es sich um die Zerlegung der Objektfelder in Variablen, die dann weiteren Optimierungen unterzogen werden. Einige von ihnen werden möglicherweise als nicht verwendet entfernt, andere werden möglicherweise mit den Quellvariablen gefaltet, mit denen sie initialisiert wurden, andere werden auf CPU-Register abgebildet. Das Ergebnis entspricht selten dem, was sich die Leute vorstellen, wenn sie "auf dem Stapel" sagen.
Holger

27

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.


2
Ich denke, Ihre Antwort wird durch einen wichtigen Verwendungszweck verkürzt, es ist ein Zwischenschritt während eines Refactors. Es kann nützlich sein, den Bereich einzuschränken und zu wissen, dass Ihr Code noch funktioniert, bevor Sie eine Methode extrahieren.
RubberDuck

16
@RubberDuck wahr, aber in diesem Fall sollte der Rest von uns es nie sehen, damit es egal ist, was wir denken. Was ein erwachsener Programmierer und ein zustimmender Compiler in der Privatsphäre ihrer eigenen Kabine tun, ist niemand anderem ein Anliegen.
candied_orange

@ Kandied_orange Ich fürchte, ich bekomme es nicht :( Pflege zu erarbeiten?
doubleOrt

@doubleOrt Ich habe gemeint, dass zwischenzeitliche Refactoring-Schritte real und notwendig sind, aber auf keinen Fall in der Quellcodeverwaltung verankert sein müssen, wo jeder darauf zugreifen kann. Beenden Sie
einfach die Umgestaltung,

@candied_orange Oh, ich dachte du würdest streiten und RubberDucks Argument nicht erweitern.
DoubleOrt

17

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 switchAussage! Manchmal möchten Sie eine Variable in einem deklarieren case:

switch (op) {
    case ADD:
        int result = a + b;
        System.out.printf("%s + %s = %s\n", a, b, result);
        break;
    case SUB:
        int result = a - b;
        System.out.printf("%s - %s = %s\n", a, b, result);
        break;
}

Dies wird jedoch fehlschlagen:

error: variable result is already defined in method main(String[])
            int result = a - b;

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:

switch (op) {
    case ADD: {
        int result = a + b;
        System.out.printf("%s + %s = %s\n", a, b, result);
    } break;
    case SUB: {
        int result = a - b;
        System.out.printf("%s - %s = %s\n", a, b, result);
    } break;
}

6
  • Allgemeiner Fall:

Gibt es einen Fall, in dem die Verwendung von Blöcken nur zur Reduzierung des Variablenumfangs sinnvoll ist?

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.

  • Ihr Anwendungsfall:

Hier ist dein Code:

KeyManagerFactory keyManager = KeyManagerFactory.getInstance("SunX509");
Keystore keyStore = KeyStore.getInstance("JKS");

{
    char[] password = getPassword();
    keyStore.load(new FileInputStream(keyStoreLocation), password);
    keyManager.init(keyStore, password);
}

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):

KeyManagerFactory keyManager = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
Keystore keyStore = KeyStore.getInstance("JKS");

try (InputStream fis = new FileInputStream(keyStoreLocation)) {
    char[] password = getPassword();
    keyStore.load(fis, password);
    keyManager.init(keyStore, password);
}

(Es ist übrigens auch oft besser, getDefaultAlgorithm()statt zu verwenden SunX509.)

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).


"Das Verringern des Gültigkeitsbereichs einiger Variablen kann ein Zeichen dafür sein, dass Ihre Methode ziemlich lang ist" - Es kann ein Zeichen sein, aber es kann auch und IMHO sollte verwendet werden, um zu dokumentieren, wie lange der Wert einer lokalen Variablen verwendet wird. Für Sprachen, die keine verschachtelten Bereiche unterstützen (z. B. Python), würde ich es begrüßen, wenn ein entsprechender Codekommentar das "Lebensende" einer Variablen angibt. Sollte dieselbe Variable später wiederverwendet werden, ist es einfach herauszufinden, ob sie vor (aber nach dem Kommentar zum "Lebensende") irgendwo mit einem neuen Wert initialisiert worden sein muss (oder sollte).
Jonny Dee

2

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 tryBlock 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 soll count, 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, sehen countund 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:

ImageIO.write(new Robot("borg").createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize())), "png", new File(Db.getUserId(User.handle()).toString()));

... 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.


Als letzte Einschränkung verwende ich, wie bereits erwähnt, von Zeit zu Zeit immer noch anonyme Blöcke, häufig in den Haupteinstiegspunktmethoden für Ausschusscode. Jemand anders hat es als Refactoring-Tool erwähnt, und ich denke, das ist in Ordnung, da das Ergebnis ein Zwischenergebnis ist und dazu gedacht ist, den Code weiter zu refactoren, nicht den finalisierten Code. Ich schätze ihre Nützlichkeit nicht gänzlich ein, aber wenn Sie versuchen, Code zu schreiben, der allgemein akzeptiert, gut getestet und korrekt ist, sehe ich kaum die Notwendigkeit anonymer Blöcke. Wenn Sie davon besessen sind, sie zu verwenden, kann sich der Fokus von klareren Funktionen mit natürlich kleinen Blöcken entfernen.

2

Gibt es einen Fall, in dem die Verwendung von Blöcken nur zur Reduzierung des Variablenumfangs sinnvoll ist?

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

  • Wenn eine Funktion nur von einer einzigen Stelle aus aufgerufen wird, sollten Sie sie mit einem Inlining versehen.
  • Wenn eine Funktion von mehreren Stellen aus aufgerufen wird, prüfen Sie, ob es möglich ist, die Arbeit an einer einzelnen Stelle, möglicherweise mit Flags, auszuführen, und fügen Sie dies ein.
  • Wenn es mehrere Versionen einer Funktion gibt, sollten Sie in Betracht ziehen, eine einzelne Funktion mit mehreren, möglicherweise voreingestellten Parametern zu erstellen.
  • Wenn die Arbeit nahezu rein funktional ist und nur wenige Verweise auf den globalen Status enthält, versuchen Sie, sie vollständig funktionsfähig zu machen.

So denkt , machen Sie eine Wahl mit dem Zweck, und (optional) leave Beweise zur Beschreibung Ihrer Motivation für die Wahl , die Sie gemacht.


1

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:

{
    char[] password = getPassword();
    try{
        keyStore.load(new FileInputStream(keyStoreLocation), password);
        keyManager.init(keyStore, password);
    }finally{
        Arrays.fill(password, '\0');
    }
}

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:

{
    MethodBuilder m_ToString = tb.DefineMethod("ToString", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final, typeofString, Type.EmptyTypes);
    var il = m_ToString.GetILGenerator();
    il.Emit(OpCodes.Ldstr, templateType.ToString()+":"+staticType.ToString());
    il.Emit(OpCodes.Ret);
    tb.DefineMethodOverride(m_ToString, m_Object_ToString);
}
{
    PropertyBuilder p_Class = tb.DefineProperty("Class", PropertyAttributes.None, typeofType, Type.EmptyTypes);
    MethodBuilder m_get_Class = tb.DefineMethod("get_Class", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final, typeofType, Type.EmptyTypes);
    var il = m_get_Class.GetILGenerator();
    il.Emit(OpCodes.Ldtoken, staticType);
    il.Emit(OpCodes.Call, m_Type_GetTypeFromHandle);
    il.Emit(OpCodes.Ret);
    p_Class.SetGetMethod(m_get_Class);
    tb.DefineMethodOverride(m_get_Class, m_IPattern_get_Class);
}

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 bin ilund 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.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.