Warum hat Java einen Compilerfehler "nicht erreichbare Anweisung"?

82

Ich finde oft, dass es beim Debuggen eines Programms praktisch ist (obwohl dies wohl eine schlechte Praxis ist), eine return-Anweisung in einen Codeblock einzufügen. Ich könnte so etwas in Java versuchen ...

class Test {
        public static void main(String args[]) {
                System.out.println("hello world");
                return;
                System.out.println("i think this line might cause a problem");
        }
}

Dies würde natürlich den Compilerfehler ergeben.

Test.java:7: nicht erreichbare Anweisung

Ich könnte verstehen, warum eine Warnung gerechtfertigt sein könnte, da unbenutzter Code eine schlechte Praxis ist. Aber ich verstehe nicht, warum dies einen Fehler erzeugen muss.

Versucht dies nur Java, ein Kindermädchen zu sein, oder gibt es einen guten Grund, dies zu einem Compilerfehler zu machen?

Mike
quelle
10
Auch hier ist Java nicht ganz konsistent. Für einige Kontrollflüsse, die toten Code verursachen, beschwert sich Java jedoch nicht. Für andere schon. Es ist ein nicht berechenbares Problem, festzustellen, welcher Code tot ist. Ich weiß nicht, warum Java beschlossen hat, etwas zu starten, das nicht beendet werden konnte.
Paul Draper

Antworten:

62

Weil nicht erreichbarer Code für den Compiler bedeutungslos ist. Während es sowohl von größter Bedeutung als auch schwieriger ist, Code für Menschen aussagekräftig zu machen, als ihn für einen Compiler sinnvoll zu machen, ist der Compiler der wesentliche Konsument von Code. Die Entwickler von Java sind der Ansicht, dass Code, der für den Compiler nicht von Bedeutung ist, ein Fehler ist. Ihre Haltung ist, dass Sie einen Fehler gemacht haben, der behoben werden muss, wenn Sie einen nicht erreichbaren Code haben.

Hier gibt es eine ähnliche Frage: Nicht erreichbarer Code: Fehler oder Warnung? , in dem der Autor sagt: "Ich persönlich bin der festen Überzeugung, dass es ein Fehler sein sollte: Wenn der Programmierer einen Code schreibt, sollte er immer die Absicht haben, ihn in einem bestimmten Szenario tatsächlich auszuführen." Offensichtlich sind sich die Sprachdesigner von Java einig.

Ob nicht erreichbarer Code die Kompilierung verhindern soll, ist eine Frage, über die es niemals einen Konsens geben wird. Aber deshalb haben es die Java-Designer getan.


Eine Reihe von Personen in Kommentaren weist darauf hin, dass es viele Klassen von nicht erreichbarem Code gibt. Java verhindert das Kompilieren nicht. Wenn ich die Konsequenzen von Gödel richtig verstehe, kann kein Compiler möglicherweise alle Klassen von nicht erreichbarem Code abfangen.

Unit-Tests können nicht jeden einzelnen Fehler erkennen. Wir verwenden dies nicht als Argument gegen ihren Wert. Ebenso kann ein Compiler nicht den gesamten problematischen Code abfangen, aber es ist dennoch wertvoll, um das Kompilieren von fehlerhaftem Code zu verhindern, wenn dies möglich ist.

Die Java-Sprachdesigner betrachten nicht erreichbaren Code als Fehler. Es ist also sinnvoll, das Kompilieren nach Möglichkeit zu verhindern.


(Bevor Sie abstimmen: Die Frage ist nicht, ob Java einen nicht erreichbaren Anweisungs-Compiler-Fehler haben sollte oder nicht. Die Frage ist, warum Java einen nicht erreichbaren Anweisungs-Compiler-Fehler hat. Stimmen Sie mich nicht ab, nur weil Sie glauben, dass Java die falsche Entwurfsentscheidung getroffen hat.)

SamStephens
quelle
22
Offensichtlich sind sich die Sprachdesigner von Java einig, dass "Sprachdesigner von Java" Menschen sind und auch fehleranfällig sind. Persönlich bin ich der Meinung, dass die andere Seite der Diskussion, auf die Sie sich beziehen, viel stärkere Argumente hat.
Nikita Rybak
6
@Gabe: Weil es nicht harmlos ist - es ist mit ziemlicher Sicherheit ein Fehler. Entweder haben Sie Ihren Code an der falschen Stelle abgelegt oder Sie haben falsch verstanden, dass Sie Ihre Erklärung so geschrieben haben, dass ein Teil davon nicht erreicht werden kann. Wenn Sie dies zu einem Fehler machen, wird verhindert, dass falscher Code geschrieben wird. Wenn Sie möchten, dass etwas an einer nicht erreichbaren Stelle in Ihrem Code von anderen Entwicklern gelesen wird (die einzige Zielgruppe für nicht erreichbaren Code), verwenden Sie stattdessen einen Kommentar.
SamStephens
10
SamStephens: Angesichts der Tatsache, dass nicht aufgerufene Methoden und nicht verwendete Variablen im Wesentlichen alle Formen von nicht erreichbarem Code sind. Warum einige Formen zulassen und andere nicht? Warum sollte ein so nützlicher Debugging-Mechanismus nicht zugelassen werden?
Gabe
5
Sind Kommentare nicht auch nicht erreichbar? In meinen Augen ist nicht erreichbarer Code tatsächlich eine Form von Kommentaren (das habe ich früher versucht usw.). Aber wenn man bedenkt, dass "echte" Kommentare auch nicht "erreichbar" sind ... vielleicht sollten sie auch diesen Fehler auslösen;)
Brady Moritz
10
Das Problem ist, dass sie (Java-Sprachdesigner) damit nicht übereinstimmen. Wenn es eine tatsächliche Flussanalyse gibt, ist es trivial zu erkennen, dass beide return; System.out.println();und if(true){ return; } System.out.println();garantiert dasselbe Verhalten aufweisen, aber einer ist ein Kompilierungsfehler und der andere nicht. Wenn ich also debugge und mit dieser Methode Code vorübergehend "auskommentiere", mache ich das so. Das Problem beim Auskommentieren von Code besteht darin, dass Sie den geeigneten Ort finden müssen, um Ihren Kommentar zu stoppen. Dies kann länger dauern, als wenn Sie einen return;schnellen Vorgang ausführen .
LadyCailin
46

Es gibt keinen endgültigen Grund, warum nicht erreichbare Aussagen nicht erlaubt sein dürfen; andere Sprachen erlauben sie ohne Probleme. Für Ihren speziellen Bedarf ist dies der übliche Trick:

if (true) return;

Es sieht unsinnig aus, jeder, der den Code liest, wird vermuten, dass er absichtlich gemacht wurde, nicht ein unachtsamer Fehler, den Rest der Aussagen unerreichbar zu lassen.

Java unterstützt ein wenig die "bedingte Kompilierung"

http://java.sun.com/docs/books/jls/third_edition/html/statements.html#14.21

if (false) { x=3; }

führt nicht zu einem Fehler bei der Kompilierung. Ein optimierender Compiler kann erkennen, dass die Anweisung x = 3; wird niemals ausgeführt und kann den Code für diese Anweisung aus der generierten Klassendatei weglassen, aber die Anweisung x = 3; wird im hier angegebenen technischen Sinne nicht als "nicht erreichbar" angesehen.

Der Grund für diese unterschiedliche Behandlung besteht darin, Programmierern die Definition von "Flag-Variablen" zu ermöglichen, wie z.

static final boolean DEBUG = false;

und schreiben Sie dann Code wie:

if (DEBUG) { x=3; }

Die Idee ist, dass es möglich sein sollte, den Wert von DEBUG von false in true oder von true in false zu ändern und dann den Code ohne weitere Änderungen am Programmtext korrekt zu kompilieren.

unwiderlegbar
quelle
2
Verstehe immer noch nicht, warum du dich mit dem Trick beschäftigen würdest, den du zeigst. Nicht erreichbarer Code ist für den Compiler bedeutungslos. Das einzige Publikum dafür sind also Entwickler. Verwenden Sie einen Kommentar. Obwohl ich denke, wenn Sie vorübergehend eine Rückgabe hinzufügen, ist die Verwendung Ihrer Problemumgehung möglicherweise etwas schneller als nur die Eingabe einer Rückgabe und das Auskommentieren des folgenden Codes.
SamStephens
8
@SamStephens Aber jedes Mal, wenn Sie wechseln wollten, mussten Sie sich an alle Stellen erinnern, an denen Sie Code auskommentieren müssen und an denen Sie das Gegenteil tun müssen. Sie müssten die gesamte Datei durchlesen, den Quellcode ändern und wahrscheinlich ab und zu subtile Fehler machen, anstatt nur "true" an einer Stelle durch "false" zu ersetzen. Ich denke, es ist besser, die Schaltlogik einmal zu codieren und sie nur zu berühren, wenn dies erforderlich ist.
Robert
"Jeder, der den Code liest, wird vermuten, dass er absichtlich gemacht wurde". Dies ist genau das Problem meiner Meinung nach, die Leute sollten nicht raten, sie sollten verstehen. Das Erinnern an Code ist die Kommunikation mit Menschen, nicht nur Anweisungen für Computer. Wenn es etwas anderes als extrem vorübergehend ist, sollten Sie meiner Meinung nach die Konfiguration verwenden. Ein Blick auf das, was Sie oben zeigen, if (true) return;zeigt nicht an, WARUM Sie die Logik überspringen, während if (BEHAVIOURDISABLED) return;die Absicht kommuniziert wird.
SamStephens
5
Dieser Trick ist sehr nützlich für das Debuggen. Ich füge oft eine if (true) -Rückgabe hinzu, um "den Rest" einer Methode zu eliminieren, die versucht herauszufinden, wo sie fehlschlägt. Es gibt andere Möglichkeiten, aber das ist sauber und schnell - aber es ist nicht etwas, das Sie jemals einchecken sollten.
Bill K
18

Es ist Nanny. Ich habe das Gefühl, dass .Net dies richtig verstanden hat - es gibt eine Warnung für nicht erreichbaren Code aus, aber keinen Fehler. Es ist gut, davor gewarnt zu werden, aber ich sehe keinen Grund, die Kompilierung zu verhindern (insbesondere während Debugging-Sitzungen, bei denen es hilfreich ist, eine Rückgabe zu werfen, um Code zu umgehen).

Brady Moritz
quelle
1
Java wurde viel früher entwickelt, jedes Byte zählt zu dieser Zeit, Disketten waren immer noch High-Tech.
unwiderlegbar
1
Für die vorzeitige Rückkehr zum Debuggen dauert es weitere fünf Sekunden, um die Zeilen nach Ihrer frühen Rückkehr zu kommentieren. Ein kleiner Preis für die Fehler, die verhindert werden, indem nicht erreichbarer Code zu einem Fehler wird.
SamStephens
6
Es ist für den Compiler auch einfach, nicht erreichbaren Code auszuschließen. Kein Grund, den Entwickler dazu zu zwingen.
Brady Moritz
2
@SamStephens nicht, wenn Sie dort bereits Kommentare haben, da Kommentare nicht gestapelt werden, ist es unnötig schmerzhaft, dies mit Kommentaren zu umgehen.
Enrey
@SamStephens Auskommentierter Code wird nicht gut umgestaltet.
Cowlinator
14

Ich habe diese Frage gerade erst bemerkt und wollte meine $ .02 dazu hinzufügen.

Im Fall von Java ist dies eigentlich keine Option. Der Fehler "Nicht erreichbarer Code" beruht nicht auf der Tatsache, dass JVM-Entwickler dachten, Entwickler vor irgendetwas zu schützen oder besonders wachsam zu sein, sondern auf den Anforderungen der JVM-Spezifikation.

Sowohl der Java-Compiler als auch JVM verwenden sogenannte "Stack-Maps" - eine eindeutige Information über alle Elemente auf dem Stack, die für die aktuelle Methode zugewiesen wurden. Der Typ jedes einzelnen Steckplatzes des Stapels muss bekannt sein, damit ein JVM-Befehl Elemente eines Typs nicht mit einem anderen Typ misshandelt. Dies ist vor allem wichtig, um zu verhindern, dass jemals ein numerischer Wert als Zeiger verwendet wird. Mit der Java-Assembly können Sie versuchen, eine Nummer zu pushen / speichern, dann aber eine Objektreferenz einfügen / laden. JVM lehnt diesen Code jedoch während der Klassenüberprüfung ab. In diesem Fall werden Stapelzuordnungen erstellt und auf Konsistenz getestet.

Um die Stapelzuordnungen zu überprüfen, muss die VM alle in einer Methode vorhandenen Codepfade durchlaufen und sicherstellen, dass die Stapeldaten für jede Anweisung unabhängig davon, welcher Codepfad jemals ausgeführt wird, mit dem übereinstimmen, was der vorherige Code gepusht hat / im Stapel gespeichert. Also, im einfachen Fall von:

Object a;
if (something) { a = new Object(); } else { a = new String(); }
System.out.println(a);

In Zeile 3 prüft JVM, ob beide Zweige von 'if' nur in einem (nur lokalen var # 0) Objekt gespeichert sind, das mit Object kompatibel ist (da Code ab Zeile 3 auf diese Weise lokales var # 0 behandelt ).

Wenn der Compiler zu einem nicht erreichbaren Code gelangt, weiß er nicht genau, welchen Status der Stapel zu diesem Zeitpunkt haben könnte, sodass er seinen Status nicht überprüfen kann. Zu diesem Zeitpunkt kann der Code nicht mehr vollständig kompiliert werden, da er auch die lokalen Variablen nicht verfolgen kann. Anstatt diese Mehrdeutigkeit in der Klassendatei zu belassen, wird ein schwerwiegender Fehler ausgegeben.

Natürlich wird eine einfache Bedingung if (1<2)es täuschen, aber es täuscht nicht wirklich - es gibt ihm einen potenziellen Zweig, der zum Code führen kann, und zumindest können sowohl der Compiler als auch die VM bestimmen, wie die Stapelelemente von dort aus verwendet werden können auf.

PS Ich weiß nicht, was .NET in diesem Fall tut, aber ich glaube, dass die Kompilierung ebenfalls fehlschlagen wird. Dies ist normalerweise kein Problem für Maschinencode-Compiler (C, C ++, Obj-C usw.).

Pawel Veselov
quelle
Wie aggressiv ist Javas Warnsystem für toten Code? Könnte es als Teil der Grammatik ausgedrückt werden (zB { [flowingstatement;]* }=> flowingstatement, { [flowingstatement;]* stoppingstatement; }=> stoppingstatement, return=> stoppingstatement, if (condition) stoppingstatement; else stoppingstatement=> stoppingstatement;usw.?
Superkatze
Ich bin nicht gut mit Grammatik, aber ich glaube schon. Das "Stoppen" kann jedoch lokal sein, beispielsweise eine "break" -Anweisung innerhalb einer Schleife. Das Ende einer Methode ist auch eine implizite Stoppanweisung für die Methodenebene, wenn die Methode ungültig ist. 'throw' ist auch eine explizite Stoppanweisung.
Pawel Veselov
Was würde der Java-Standard über so etwas sagen wie void blah() { try {return;} catch (RuntimeError ex) { } DoSomethingElse(); } Würde Java kreischen, dass es keine Möglichkeit gibt, den tryBlock tatsächlich über eine Ausnahme zu beenden, oder würde er annehmen, dass irgendeine Art von ungeprüfter Ausnahme innerhalb eines tryBlocks auftreten könnte ?
Supercat
Der beste Weg, dies herauszufinden, besteht darin, dies zu kompilieren. Der Compiler erlaubt jedoch auch leere try / catch-Anweisungen. Jeder JVM-Befehl kann jedoch theoretisch eine Ausnahme auslösen, sodass der try-Block möglicherweise beendet wird. Aber ich bin mir nicht sicher, wie dies für diese Frage relevant ist.
Pawel Veselov
Grundsätzlich stellt sich die Frage, ob die einzigen verbotenen "nicht erreichbaren" Aussagen diejenigen sind, die auf der Grundlage des Parsens als nicht erreichbar identifiziert werden könnten, ohne tatsächlich Ausdrücke bewerten zu müssen, oder ob der Standard nicht erreichbare Aussagen wie verbieten könnte if (constantThatEqualsFive < 3) {doSomething;};.
Supercat
5

Eines der Ziele von Compilern ist es, Fehlerklassen auszuschließen. Es ist aus Versehen ein nicht erreichbarer Code vorhanden. Es ist schön, dass javac diese Fehlerklasse beim Kompilieren ausschließt.

Für jede Regel, die fehlerhaften Code abfängt, möchte jemand, dass der Compiler ihn akzeptiert, weil er weiß, was er tut. Das ist die Strafe für die Compilerprüfung, und das richtige Gleichgewicht zu finden, ist einer der Trickpunkte beim Sprachdesign. Selbst bei strengster Überprüfung können immer noch unendlich viele Programme geschrieben werden, so dass es nicht so schlimm sein kann.

Ricky Clarkson
quelle
5

Obwohl ich denke, dass dieser Compilerfehler eine gute Sache ist, gibt es eine Möglichkeit, ihn zu umgehen. Verwenden Sie eine Bedingung, von der Sie wissen, dass sie wahr ist:

public void myMethod(){

    someCodeHere();

    if(1 < 2) return; // compiler isn't smart enough to complain about this

    moreCodeHere();

}

Der Compiler ist nicht klug genug, sich darüber zu beschweren.

Sean Patrick Floyd
quelle
6
Die Frage hier ist warum? Nicht erreichbarer Code ist für den Compiler bedeutungslos. Das einzige Publikum dafür sind also Entwickler. Verwenden Sie einen Kommentar.
SamStephens
3
Also bekomme ich Downvotes, weil ich mit der Art und Weise einverstanden bin, wie die Java-Jungs ihre Kompilierung entworfen haben? Herrgott ...
Sean Patrick Floyd
1
Sie erhalten Abstimmungen, wenn Sie die eigentliche Frage nicht beantworten. Siehe den Kommentar von SamStephens.
BoltClock
2
javac kann definitiv schließen, dass dies 1<2wahr ist, und der Rest der Methode muss nicht im Bytecode enthalten sein. Die Regeln für "nicht erreichbare Anweisungen" müssen klar, fest und unabhängig von der Intelligenz von Compilern sein.
unwiderlegbar
1
@Sam mit dieser Einstellung müssen Sie 50% der besten Antworten dieser Website herabstimmen (und ich sage nicht, dass meine Antwort unter diesen ist). Ein großer Teil der Beantwortung von Fragen zu SO besteht, wie in jeder IT-Beratungssituation, darin, die eigentliche Frage (oder relevante Nebenfragen) aus einer bestimmten Frage zu identifizieren.
Sean Patrick Floyd
0

Es ist sicherlich eine gute Sache, sich zu beschweren, je strenger der Compiler ist, desto besser, soweit es Ihnen ermöglicht, das zu tun, was Sie brauchen. Normalerweise ist der geringe Preis, den Code zu kommentieren, der Vorteil, dass beim Kompilieren Ihr Code funktioniert. Ein allgemeines Beispiel ist Haskell, über das die Leute schreien, bis sie erkennen, dass ihr Test / Debugging nur ein Haupttest und ein kurzer Test ist. Ich persönlich mache in Java fast kein Debugging, während ich (tatsächlich absichtlich) nicht aufmerksam bin.

Jérôme JEAN-CHARLES
quelle
0

Wenn der Grund für das Zulassen darin if (aBooleanVariable) return; someMoreCode;besteht, Flags zuzulassen, if (true) return; someMoreCode;scheint die Tatsache, dass kein Kompilierungszeitfehler generiert wird, eine Inkonsistenz in der Richtlinie zum Generieren der CodeNotReachable-Ausnahme zu sein, da der Compiler weiß, dass dies truekein Flag (keine Variable) ist.

Zwei andere Möglichkeiten, die interessant sein könnten, aber nicht für das Ausschalten eines Teils des Codes einer Methode gelten, sowie if (true) return:

Anstatt zu sagen if (true) return;, möchten Sie jetzt vielleicht sagen assert falseund hinzufügen-ea OR -ea package OR -ea className zu den jvm-Argumenten . Der gute Punkt ist, dass dies eine gewisse Granularität ermöglicht und das Hinzufügen eines zusätzlichen Parameters zum JVM-Aufruf erfordert, sodass kein DEBUG-Flag im Code gesetzt werden muss, sondern ein zusätzliches Argument zur Laufzeit, was nützlich ist, wenn das Ziel nicht das ist Entwicklermaschine und das Neukompilieren und Übertragen von Bytecode braucht Zeit.

Es gibt auch den System.exit(0)Weg, aber dies könnte ein Overkill sein. Wenn Sie es in Java in einer JSP ablegen, wird der Server beendet.

Abgesehen davon, dass Java von Natur aus eine 'Nanny'-Sprache ist, würde ich lieber etwas Natives wie C / C ++ verwenden, um mehr Kontrolle zu haben.

MichaelS
quelle
Das Ändern von etwas von einer static finalVariablen, die in einem statischen Initialisierungsblock festgelegt ist, zu einer static finalVariablen, die in seiner Deklaration festgelegt ist, sollte keine grundlegende Änderung sein. Es ist durchaus plausibel, dass eine Klasse beim ersten Schreiben manchmal nicht in der Lage war, etwas zu tun (und erst zur Laufzeit wusste, ob sie dazu in der Lage wäre oder nicht), aber spätere Versionen der Klasse waren möglicherweise dazu in der Lage Mach diese Aktion immer. Das Ändern myClass.canFooeiner Konstante sollte if (myClass.canFoo) myClass.Foo(); else doSomethingElse();effizienter sein - nicht brechen.
Supercat