Ist die Verwendung einer beschrifteten Unterbrechung in Java eine gute Vorgehensweise?

80

Ich starre auf einen alten Code aus dem Jahr 2001 und bin auf diese Aussage gestoßen:

   else {
     do {
       int c = XMLDocumentFragmentScannerImpl.this.scanContent();
       if (c == 60) {
         XMLDocumentFragmentScannerImpl.this.fEntityScanner.scanChar();
         XMLDocumentFragmentScannerImpl.this.setScannerState(1);
         break label913;
       }

Ich hatte das noch nie gesehen und hier beschriftete Brüche entdeckt:

http://docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html

Funktioniert das nicht im Wesentlichen so goto? Ist es überhaupt eine gute Praxis, es zu benutzen? Es macht mich unruhig.

avgvstvs
quelle
2
wo ist label913definiert?
Nikolay Kuznetsov
1
In diesem Fall etwa 200 Zeilen später am Ende einer ziemlich großen switch-Anweisung.
avgvstvs
Weiter und Label Name macht den Code, um den Teil des Codes zu verarbeiten, in dem der Block mit einem Label markiert wurde
user_CC
5
In diesem Fall liegt das Problem also in der Größe der switch-Anweisung!
Cyrille Ka
7
Dies sieht aus wie generierter Code (eigentlich ein Parser). Es gibt viele Tricks, die mit generiertem Code verwendet werden und in handgeschriebenem Code nicht verwendet werden (hauptsächlich, weil sie die Generierung erleichtern).
Parsifal

Antworten:

118

Nein, es ist nicht wie ein Goto, da Sie nicht zu einem anderen Teil des Kontrollflusses "gehen" können.

Von der Seite, die Sie verlinkt haben:

Die break-Anweisung beendet die beschriftete Anweisung. Der Kontrollfluss wird nicht auf das Etikett übertragen. Der Kontrollfluss wird unmittelbar nach der gekennzeichneten (beendeten) Anweisung auf die Anweisung übertragen.

Dies bedeutet, dass Sie nur Schleifen unterbrechen können, die gerade ausgeführt werden.

Betrachten Sie dieses Beispiel:

first:
for( int i = 0; i < 10; i++) {
  second:
  for(int j = 0; j < 5; j ++ )
  {
    break xxx;
  }
}

third:
for( int a = 0; a < 10; a++) {

}

Sie könnten ersetzen xxxmit firstoder second(die äußere oder innere Schleife zu brechen), da beide Schleifen ausgeführt werden, wenn Sie die Hit - breakAnweisung, wobei jedoch xxxmit thirdnicht kompiliert.

Thomas
quelle
docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html für den Fall, dass beim Kombinieren mit Labels
fortgefahren
@user_CC Nicht wirklich, du kannst immer noch nicht aus dem Kontrollfluss springen. Ein beschriftetes Fortsetzen startet nur die nächste Iteration der beschrifteten Schleife und funktioniert nur, während diese Schleife bereits ausgeführt wird.
Thomas
3
Wenn ich es also verwenden break first;würde, würde der Compiler sofort wieder in die Zeile zurückkehren first:und diese Schleifen erneut ausführen?
Ungeheuer
15
@JohnnyCoder nein, break first;würde brechen (end) die Schleife markiert firstdh es würde weiterhin mit third. Auf der anderen Seite continue first;würde die aktuelle Iteration von beenden firstund damit brechen secondund mit dem nächsten Wert von iin fortfahren first.
Thomas
15

Es ist nicht so schrecklich, gotoweil es nur die Kontrolle an das Ende der beschrifteten Anweisung sendet (normalerweise ein Schleifenkonstrukt). gotoWas unwahrscheinlich macht, ist, dass es sich um einen beliebigen Zweig zu einem beliebigen Ort handelt, einschließlich Beschriftungen, die sich weiter oben in der Methodenquelle befinden, sodass Sie ein selbst entwickeltes Schleifenverhalten haben können. Die Label-Break-Funktionalität in Java lässt diese Art von Verrücktheit nicht zu, da die Steuerung nur vorwärts geht.

Ich habe es vor ungefähr 12 Jahren nur einmal verwendet. In einer Situation, in der ich aus verschachtelten Schleifen ausbrechen musste, hätte die strukturiertere Alternative zu komplizierteren Tests in den Schleifen geführt. Ich würde nicht empfehlen, es häufig zu verwenden, aber ich würde es nicht als automatischen Geruch nach schlechtem Code kennzeichnen.

Nathan Hughes
quelle
6
@ Boann: Ich will nicht behaupten, dass goto in allen Kontexten insgesamt böse ist. Es ist nur eines dieser Dinge wie Zeigerarithmetik, Überladung von Operatoren und explizite Speicherverwaltung, von denen die Entwickler von Java entschieden haben, dass sie die Mühe nicht wert waren. Und nachdem ich COBOL-Programme gelesen habe, die zu über 90% aus Gotos bestanden, bin ich nicht traurig, dass sie Java weggelassen haben.
Nathan Hughes
1
Oh, du hattest beim ersten Mal Recht @NathanHughes, goto ist verdammt böse. Natürlich nicht in allen Situationen, aber ja, ich jedenfalls vermisse es nicht.
Wahrnehmung
2
goto ist vielleicht "nützlicher" als Etiketten, @Boann, aber das wird durch die Wartungskosten völlig aufgewogen. Labels sind nicht der Zauberstab, den Gotos sind, und das ist gut so.
DavidS
9

Es ist immer möglich, durch eine breakneue Methode zu ersetzen .

Betrachten Sie die Codeprüfung für alle gemeinsamen Elemente in zwei Listen:

List list1, list2;

boolean foundCommonElement = false;
for (Object el : list1) {
    if (list2.contains(el)) {
        foundCommonElement = true;
        break;
    }
}

Sie können es folgendermaßen umschreiben:

boolean haveCommonElement(List list1, List list2) {
    for (Object el : list1) {
        if (list2.contains(el)) {
            return true;
        }
    }
    return false;
}

Um nach gemeinsamen Elementen zwischen zwei Listen zu suchen, ist es natürlich besser, die list1.retainAll(new HashSet<>(list2))Methode zu verwenden, um dies O(n)mit O(n)zusätzlichem Speicher zu tun , oder beide Listen nach zu sortieren O(n * log n)und dann nach gemeinsamen Elementen zu suchen O(n).

Logicray
quelle
Dies ist meiner Meinung nach fast immer der richtige Ansatz. gotoist (potenziell) schädlich, weil es Ihnen so viele Orte schicken könnte - es ist überhaupt nicht ersichtlich, was erreicht wird; Das Erstellen einer Hilfsfunktion gibt dem nächsten Benutzer klare Grenzen, was Sie versuchen und wo Sie es tun werden, und gibt Ihnen die Möglichkeit, diese Funktionalität zu benennen. Viel klarer auf diese Weise.
Ethanbustad
@ethanbustad Aber hier geht es um beschriftet break, nicht goto. Sicher, das hier gezeigte Refactoring ist in beiden Fällen eine gute Idee. Aber etikettiert breakist bei weitem nicht so unmoderiert und neigt dazu, Code zu spaghettifizieren, wie es gotoist.
underscore_d
Dieses Beispiel ist schrecklich, Pause funktioniert nicht, wenn das Etikett hier einfach nutzlos ist.
Diracnote
8

Bevor Sie den Rest dieser Antwort lesen, lesen Sie bitte Gehe zu Erklärung, die als schädlich eingestuft wird . Wenn Sie es nicht vollständig lesen möchten, halte ich Folgendes für den entscheidenden Punkt:

Die ungezügelte Verwendung der go to-Anweisung hat die unmittelbare Folge, dass es furchtbar schwierig wird, einen aussagekräftigen Satz von Koordinaten zu finden, in denen der Prozessfortschritt beschrieben werden kann.

Oder um es anders auszudrücken: Das Problem gotobesteht darin, dass das Programm in der Mitte eines Codeblocks ankommen kann, ohne dass der Programmierer den Programmstatus zu diesem Zeitpunkt versteht. Die standardmäßigen blockorientierten Konstrukte sind so konzipiert, dass sie Zustandsübergänge klar abgrenzen. Ein Label breaksoll das Programm in einen bestimmten bekannten Zustand bringen (außerhalb des enthaltenden markierten Blocks).

In einem imperativen Programm der realen Welt ist der Zustand nicht klar durch Blockgrenzen abgegrenzt, so dass es fraglich ist, ob das Etikett breakeine gute Idee ist. Wenn der Block den von außerhalb des Blocks sichtbaren Status ändert und mehrere Punkte zum Verlassen des Blocks vorhanden sind, entspricht eine Beschriftung breakdem Grundelement goto. Der einzige Unterschied besteht darin, dass Sie anstelle der Chance, in der Mitte eines Blocks mit unbestimmtem Zustand zu landen, einen neuen Block mit unbestimmtem Zustand starten.

Im Allgemeinen würde ich ein Etikett breakals gefährlich betrachten. Meiner Meinung nach ist dies ein Zeichen dafür, dass der Block in eine Funktion mit eingeschränktem Zugriff auf den umschließenden Bereich konvertiert werden sollte.

Dieser Beispielcode war jedoch eindeutig das Produkt eines Parser-Generators (das OP kommentierte, dass es sich um Xerces-Quellcode handelte). Und Parser-Generatoren (oder Code-Generatoren im Allgemeinen) nehmen sich oft Freiheiten mit dem Code, den sie generieren, weil sie den Zustand perfekt kennen und der Mensch ihn nicht verstehen muss.

Parsifal
quelle
Ich stimme nicht zu breakist bei mehreren Gelegenheiten sehr nützlich. Zum Beispiel, wenn Sie nach einem Wert suchen, der aus mehreren geordneten Quellen stammen kann. Sie versuchen es nacheinander, und wenn der erste gefunden wird, Sie break. Es ist eleganter Code als if / else if / else if / else if. (Zumindest sind es weniger Zeilen.) Es ist möglicherweise nicht immer praktisch, alles in einen Kontext einer Methode zu verschieben. Ein anderer Fall ist, wenn Sie bedingt von wenigen verschachtelten Ebenen abbrechen. Kurz gesagt, wenn Sie nicht mögen break, dann mögen Sie returnnirgendwo anders als am Ende einer Methode.
Ondra Žižka
Für den Kontext; "Gehe zu Aussage, die als schädlich angesehen wird" wurde geschrieben, als Dinge wie dound whilenicht existierten und jeder stattdessen if(.. ) gotoüberall benutzte . Es sollte Sprachdesigner ermutigen, Unterstützung für Dinge wie dound hinzuzufügen while. Als der Zug anfing zu rollen, entwickelte er sich leider zu einem Hype-Zug, der von unwissenden Leuten angetrieben wurde, die Dinge wie break"nur einmal ausgeführt" -Schleifen steckten und Ausnahmen für diejenigen gotomachten , die wissen, was, für alle (seltenen) Fälle, die sauberer und weniger schädlich sind.
Brendan
1

Dies ist nicht wie eine gotoAussage, bei der Sie die Flusskontrolle rückwärts springen. Ein Etikett zeigt Ihnen (dem Programmierer) lediglich, wo die Unterbrechung aufgetreten ist. Außerdem wird die Flusskontrolle auf die nächste Anweisung nach dem übertragen break.

Ich persönlich denke, dass es nicht von großem Nutzen ist, wenn Sie Code schreiben, der Tausende von Zeilen wert ist, wird er unbedeutend. Aber auch hier würde es vom Anwendungsfall abhängen.

Nomade
quelle
for(int i = 0; i < 1; i++) { do_something(); if(I_want_to_go_backwards) { i = 0; continue; } }.
Brendan
1

Eine sauberere Art, die Absicht auszudrücken, könnte zumindest meiner Meinung nach darin bestehen, das Codefragment, das die Schleife enthält, in eine separate Methode zu setzen und einfach returndaraus.

Ändern Sie dies beispielsweise:

someLabel:
for (int j = 0; j < 5; j++)
{
    // do something
    if ...
        break someLabel;
}

das mögen:

private void Foo() {
    for (int j = 0; j < 5; j++)
    {
        // do something
        if ...
            return;
    }
}

Dies ist auch idiomatischer für Entwickler, die andere Sprachen beherrschen und möglicherweise in Zukunft (oder in Zukunft mit Ihrem Code ) arbeiten.

Marek
quelle