Dlaczego w Javie pojawia się błąd kompilatora „nieosiągalna instrukcja”?

84

Często stwierdzam, że podczas debugowania programu wygodne jest (choć może to być złą praktyką) wstawienie instrukcji return wewnątrz bloku kodu. Mogę spróbować czegoś takiego w Javie ...

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");
        }
}

oczywiście spowodowałoby to błąd kompilatora.

Test.java:7: nieosiągalne oświadczenie

Mogłem zrozumieć, dlaczego ostrzeżenie może być uzasadnione, ponieważ posiadanie nieużywanego kodu jest złą praktyką. Ale nie rozumiem, dlaczego ma to powodować błąd.

Czy to po prostu Java próbuje być nianią, czy jest dobry powód, aby uczynić to błędem kompilatora?

Mikrofon
źródło
11
Java również nie jest w tym całkowicie spójna. W przypadku niektórych przepływów sterowania, które powodują martwy kod, ale Java nie narzeka. Dla innych tak. Ustalenie, który kod jest martwy, jest problemem nieobliczalnym. Nie wiem, dlaczego Java zdecydowała się rozpocząć coś, czego nie mogła zakończyć.
Paul Draper,

Odpowiedzi:

64

Ponieważ nieosiągalny kod jest bez znaczenia dla kompilatora. Chociaż uczynienie kodu zrozumiałym dla ludzi jest zarówno najważniejsze, jak i trudniejsze niż uczynienie go zrozumiałym dla kompilatora, kompilator jest podstawowym konsumentem kodu. Projektanci Javy uważają, że kod, który nie jest zrozumiały dla kompilatora, jest błędem. Ich stanowisko jest takie, że jeśli masz jakiś nieosiągalny kod, popełniłeś błąd, który należy naprawić.

Tutaj pojawia się podobne pytanie: Nieosiągalny kod: błąd czy ostrzeżenie? , w którym autor mówi: „Osobiście uważam, że to powinien być błąd: jeśli programista pisze fragment kodu, zawsze powinien mieć zamiar uruchomić go w jakimś scenariuszu”. Oczywiście projektanci języka Java zgadzają się z tym.

Czy nieosiągalny kod powinien uniemożliwić kompilację, jest kwestią, w której nigdy nie będzie konsensusu. Ale właśnie dlatego zrobili to projektanci Java.


Wiele osób w komentarzach zwraca uwagę, że istnieje wiele klas niedostępnego kodu. Java nie zapobiega kompilacji. Jeśli dobrze rozumiem konsekwencje Gödla, żaden kompilator nie jest w stanie złapać wszystkich klas nieosiągalnego kodu.

Testy jednostkowe nie mogą wychwycić każdego pojedynczego błędu. Nie używamy tego jako argumentu przeciwko ich wartości. Podobnie kompilator nie może wychwycić całego problematycznego kodu, ale nadal jest cenny, aby zapobiec kompilacji złego kodu, gdy jest to możliwe.

Projektanci języka Java uważają nieosiągalny kod za błąd. Dlatego zapobieganie kompilacji, gdy jest to możliwe, jest rozsądne.


(Zanim zagłosujesz: nie chodzi o to, czy Java powinna mieć błąd kompilatora instrukcji nieosiągalnych, czy nie. Pytanie brzmi, dlaczego Java ma błąd kompilatora instrukcji nieosiągalnych. Nie oceniaj mnie negatywnie tylko dlatego, że uważasz, że Java podjęła złą decyzję projektową).

SamStephens
źródło
22
Oczywiście projektanci języka Java są zgodni co do tego, że „Projektanci języka Java” to ludzie i również podatni na błędy. Osobiście uważam, że druga strona dyskusji, o której pan mówi, ma znacznie silniejsze argumenty.
Nikita Rybak
6
@ Gabe: Ponieważ nie jest to nieszkodliwe - prawie na pewno jest to błąd. Albo umieściłeś kod w złym miejscu, albo źle zrozumiałeś, że napisałeś swoje oświadczenie w taki sposób, że do części z nich nie można dotrzeć. Uczynienie tego błędem zapobiega pisaniu nieprawidłowego kodu, a jeśli chcesz, aby coś w nieosiągalnym miejscu w Twoim kodzie do odczytania przez innych programistów (jedyna grupa odbiorców dla nieosiągalnego kodu), użyj komentarza.
SamStephens
10
SamStephens: Widząc, że nieużywane metody i nieużywane zmienne są zasadniczo wszystkimi formami nieosiągalnego kodu. Po co pozwalać niektórym formularzom, a innym nie? W szczególności, po co blokować taki przydatny mechanizm debugowania?
Gabe
5
Czy komentarze również nie są nieosiągalne? Moim zdaniem nieosiągalny kod jest w rzeczywistości formą komentarzy (tak właśnie próbowałem zrobić wcześniej itp.). Ale biorąc pod uwagę, że "prawdziwe" komentarze też nie są "osiągalne" ... może powinni również podnieść ten błąd;)
Brady Moritz
10
Problem polega na tym, że oni (projektanci języka Java) nie są z tym zgodni. Jeśli istnieje rzeczywista analiza przepływu, łatwo jest zdać sobie sprawę, że oba return; System.out.println();i if(true){ return; } System.out.println();mają takie samo zachowanie, ale jeden jest błędem kompilacji, a drugi nie. Tak więc, kiedy debuguję i używam tej metody do tymczasowego „komentowania” kodu, tak to robię. Problem z wykomentowaniem kodu polega na tym, że musisz znaleźć odpowiednie miejsce, aby zatrzymać komentarz, co może zająć więcej czasu niż wrzucenie return;naprawdę szybkiego.
LadyCailin
47

Nie ma ostatecznego powodu, dla którego nie można dopuścić nieosiągalnych instrukcji; inne języki pozwalają na to bez problemów. Oto zwykła sztuczka dla twojej konkretnej potrzeby:

if (true) return;

Wygląda to bezsensownie, każdy, kto czyta kod, domyśli się, że musiało to być zrobione celowo, a nie nieostrożny błąd polegający na pozostawieniu reszty wypowiedzi nieosiągalnych.

Java ma niewielkie wsparcie dla „kompilacji warunkowej”

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

if (false) { x=3; }

nie powoduje błędu w czasie kompilacji. Optymalizujący kompilator może zdać sobie sprawę, że instrukcja x = 3; nigdy nie zostanie wykonany i może zdecydować się na pominięcie kodu tej instrukcji w wygenerowanym pliku klasy, ale instrukcja x = 3; nie jest uważany za „nieosiągalny” w określonym tutaj znaczeniu technicznym.

Uzasadnieniem tego odmiennego traktowania jest umożliwienie programistom zdefiniowania „zmiennych flagowych”, takich jak:

static final boolean DEBUG = false;

a następnie napisz kod taki jak:

if (DEBUG) { x=3; }

Chodzi o to, że powinna istnieć możliwość zmiany wartości DEBUG z false na true lub z true na false, a następnie poprawnie skompilować kod bez innych zmian w tekście programu.

nieoceniony
źródło
2
Nadal nie rozumiem, dlaczego zawracasz sobie głowę sztuczką, którą pokazujesz. Nieosiągalny kod jest bez znaczenia dla kompilatora. Więc jedyną jego publicznością są programiści. Użyj komentarza. Chociaż wydaje mi się, że jeśli tymczasowo dodajesz zwrot, użycie obejścia może być o wiele szybsze niż zwykłe wprowadzenie zwrotu i skomentowanie następującego kodu.
SamStephens
8
@SamStephens Ale za każdym razem, gdy chciałeś się przełączyć, musiałbyś pamiętać wszystkie miejsca, w których musisz wykomentować kod i gdzie musisz zrobić coś odwrotnego. Musiałbyś przeczytać cały plik, zmieniając kod źródłowy, prawdopodobnie robiąc od czasu do czasu jakieś subtelne błędy, zamiast po prostu zastępować „prawda” przez „fałsz” w jednym miejscu. Myślę, że lepiej jest raz zakodować logikę przełączania i nie dotykać jej, chyba że jest to konieczne.
Robert
„każdy, kto czyta kod, domyśli się, że musiało to być zrobione celowo”. Moim zdaniem na tym właśnie polega problem, ludzie nie powinni zgadywać, powinni zrozumieć. Zapamiętywanie kodu to komunikacja z ludźmi, a nie tylko instrukcje dla komputerów. Jeśli jest to coś innego niż wyjątkowo tymczasowe, moim zdaniem powinieneś użyć konfiguracji. Patrząc na to, co pokazujesz powyżej, if (true) return;nie wskazuje, DLACZEGO pomijasz logikę, podczas gdy if (BEHAVIOURDISABLED) return;komunikujesz zamiar.
SamStephens
5
Ta sztuczka jest naprawdę przydatna do debugowania. Często dodaję zwrot if (true), aby wyeliminować „resztę” metody, próbując dowiedzieć się, gdzie zawodzi. Są inne sposoby, ale to jest czyste i szybkie - ale nie jest to coś, co powinieneś kiedykolwiek sprawdzać.
Bill K
19

To jest niania. Wydaje mi się, że .Net ma rację - generuje ostrzeżenie o nieosiągalnym kodzie, ale nie jest to błąd. Dobrze jest być przed tym ostrzeżonym, ale nie widzę powodu, aby zapobiegać kompilacji (szczególnie podczas sesji debugowania, gdzie dobrze jest wrzucić powrót, aby ominąć jakiś kod).

Brady Moritz
źródło
1
java została zaprojektowana dużo wcześniej, wtedy liczył się każdy bajt, dyskietki wciąż były zaawansowane technologicznie.
niezaprzeczalny
1
W przypadku wczesnego powrotu do debugowania wykomentowanie wierszy następujących po wczesnym powrocie zajmuje kolejne pięć sekund. Niewielka cena za błędy, którym zapobiega błąd, który uniemożliwia nieosiągalny kod.
SamStephens
6
kompilator może również łatwo wykluczyć nieosiągalny kod. nie ma powodu, aby zmuszać programistę do tego.
Brady Moritz
2
@SamStephens nie, jeśli masz już tam komentarze, ponieważ komentarze się nie kumulują, niepotrzebne jest obejście tego z komentarzami.
enrey
@SamStephens Skomentowany kod nie jest dobrze refaktoryzowany.
cowlinator
14

Dopiero co zauważyłem to pytanie i chciałem dodać do niego 0,02 $.

W przypadku Javy nie ma takiej możliwości. Błąd „nieosiągalnego kodu” nie wynika z faktu, że programiści JVM myśleli, że powinni chronić programistów przed czymkolwiek lub zachować szczególną czujność, ale z wymagań specyfikacji JVM.

Zarówno kompilator Java, jak i JVM używają tak zwanych „map stosu” - określonej informacji o wszystkich elementach stosu, przydzielonych dla bieżącej metody. Typ każdej szczeliny stosu musi być znany, aby instrukcja JVM nie traktowała elementu jednego typu dla innego typu. Jest to szczególnie ważne, aby zapobiec używaniu wartości liczbowej jako wskaźnika. Jest możliwe, używając zestawu Java, aby spróbować wypchnąć / zapisać liczbę, a następnie pobrać / załadować odwołanie do obiektu. Jednak JVM odrzuci ten kod podczas sprawdzania poprawności klas, czyli wtedy, gdy mapy stosu są tworzone i testowane pod kątem spójności.

Aby zweryfikować mapy stosu, maszyna wirtualna musi przejść przez wszystkie ścieżki kodu istniejące w metodzie i upewnić się, że bez względu na to, która ścieżka kodu zostanie kiedykolwiek wykonana, dane stosu dla każdej instrukcji są zgodne z tym, co przekazał poprzedni kod / przechowywane w stosie. A więc w prostym przypadku:

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

w linii 3, JVM sprawdzi, czy obie gałęzie 'if' zapisały tylko w (która jest po prostu lokalną zmienną # 0) czymś, co jest kompatybilne z Object (ponieważ tak kod z linii 3 i następnych będzie traktował lokalną zmienną # 0 ).

Kiedy kompilator dociera do nieosiągalnego kodu, nie do końca wie, w jakim stanie może być stos w tym momencie, więc nie może zweryfikować jego stanu. W tym momencie nie może już skompilować kodu, ponieważ nie może również śledzić zmiennych lokalnych, więc zamiast pozostawić tę niejednoznaczność w pliku klasy, generuje błąd krytyczny.

Oczywiście prosty warunek, taki jak if (1<2), oszuka go, ale tak naprawdę nie jest to głupi - daje mu potencjalną gałąź, która może prowadzić do kodu, a przynajmniej zarówno kompilator, jak i maszyna wirtualna mogą określić, w jaki sposób elementy stosu mogą być stamtąd używane na.

PS Nie wiem, co robi .NET w tym przypadku, ale sądzę, że kompilacja również się nie powiedzie. Zwykle nie będzie to problemem dla żadnych kompilatorów kodu maszynowego (C, C ++, Obj-C itp.)

Pawel Veselov
źródło
Jak agresywny jest system ostrzegania przed martwym kodem w Javie? Czy można to wyrazić jako część gramatyki (np. { [flowingstatement;]* }=> flowingstatement, { [flowingstatement;]* stoppingstatement; }=> stoppingstatement, return=> stoppingstatement, if (condition) stoppingstatement; else stoppingstatement=> stoppingstatement;Itd.?
supercat
Nie jestem dobry w gramatyce, ale uważam, że tak. Jednak „zatrzymanie” może być lokalne, na przykład instrukcja „przerwa” wewnątrz pętli. Ponadto koniec metody jest niejawną instrukcją zatrzymującą dla poziomu metody, jeśli metoda jest nieważna. „rzut” będzie również jawną instrukcją zatrzymującą.
Paweł Veselov
Co powiedziałby standard Java na temat czegoś takiego, jak void blah() { try {return;} catch (RuntimeError ex) { } DoSomethingElse(); } Czy Java wydałaby dźwięk, że nie ma możliwości wyjścia z trybloku przez wyjątek, czy też wyliczyłby, że w dowolnym trybloku mógłby wystąpić jakikolwiek niezaznaczony wyjątek ?
supercat
Najlepszym sposobem, aby się tego dowiedzieć, jest próba skompilowania tego. Jednak kompilator dopuszcza nawet puste instrukcje try / catch. Jednak każda instrukcja JVM może teoretycznie zgłosić wyjątek, więc try block może zakończyć działanie. Ale nie jestem pewien, jak to ma związek z tym pytaniem.
Pawel Veselov,
Zasadniczo pytanie brzmi, czy jedynymi zabronionymi stwierdzeniami „nieosiągalnymi” są te, które można by zidentyfikować jako nieosiągalne na podstawie analizy składniowej bez konieczności rzeczywistej oceny jakichkolwiek wyrażeń, czy też standard mógłby zakazać nieosiągalnych stwierdzeń, takich jak if (constantThatEqualsFive < 3) {doSomething;};.
supercat
5

Jednym z celów kompilatorów jest wykluczenie klas błędów. Jakiś nieosiągalny kod jest tam przez przypadek, fajnie, że javac wyklucza tę klasę błędów w czasie kompilacji.

Dla każdej reguły, która wyłapuje błędny kod, ktoś będzie chciał, aby kompilator ją zaakceptował, ponieważ wie, co robi. Taka jest kara za sprawdzanie kompilatora, a uzyskanie właściwej równowagi jest jednym z najtrudniejszych punktów projektowania języka. Nawet przy najsurowszych kontrolach wciąż istnieje nieskończona liczba programów, które można napisać, więc nie może być tak źle.

Ricky Clarkson
źródło
5

Chociaż myślę, że ten błąd kompilatora to dobra rzecz, jest sposób na obejście tego problemu. Użyj warunku, o którym wiesz, że będzie prawdziwy:

public void myMethod(){

    someCodeHere();

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

    moreCodeHere();

}

Kompilator nie jest wystarczająco inteligentny, aby narzekać na to.

Sean Patrick Floyd
źródło
6
Tutaj pojawia się pytanie, dlaczego? Nieosiągalny kod jest bez znaczenia dla kompilatora. Więc jedyną jego publicznością są programiści. Użyj komentarza.
SamStephens
3
Więc dostaję głosy negatywne, ponieważ zgadzam się ze sposobem, w jaki faceci java zaprojektowali swoją kompilację? Jezu ...
Sean Patrick Floyd
1
Otrzymujesz głosy przeciwne za brak odpowiedzi na pytanie. Zobacz komentarz SamStephens.
BoltClock
2
javac może zdecydowanie wywnioskować, że 1<2to prawda, a reszta metody nie musi być uwzględniona w kodzie bajtowym. zasady „nieosiągalnych stwierdzeń” muszą być jasne, ustalone i niezależne od sprytu kompilatorów.
niezaprzeczalny
1
@Sam z takim nastawieniem, będziesz musiał zagłosować przeciw 50% najlepszych odpowiedzi tej strony (i nie mówię, że moja odpowiedź jest jedną z nich). Duża część odpowiedzi na pytania dotyczące SO, tak jak w przypadku każdej sytuacji konsultingowej IT, polega na zidentyfikowaniu rzeczywistego pytania (lub odpowiednich pytań pobocznych) z danego pytania.
Sean Patrick Floyd
0

Z pewnością dobrze jest narzekać, im bardziej rygorystyczny kompilator jest lepszy, o ile pozwala na zrobienie tego, czego potrzebujesz. Zwykle niewielką ceną jest skomentowanie kodu, a korzyścią jest to, że kompilacja kodu działa. Ogólnym przykładem jest Haskell, o którym ludzie krzyczą, dopóki nie zdadzą sobie sprawy, że ich test / debugowanie jest tylko głównym i krótkim testem. Osobiście w Javie prawie nie debuguję, będąc (w rzeczywistości celowo) nieuważnym.

Jérôme JEAN-CHARLES
źródło
0

Jeśli powodem zezwolenia if (aBooleanVariable) return; someMoreCode;jest zezwolenie na flagi, to fakt, że if (true) return; someMoreCode;nie generuje błędu czasu kompilacji, wydaje się niespójnością w polityce generowania wyjątku CodeNotReachable, ponieważ kompilator „wie”, że truenie jest to flaga (a nie zmienna).

Dwa inne sposoby, które mogą być interesujące, ale nie mają zastosowania do wyłączania części kodu metody, a także if (true) return:

Zamiast mówić, if (true) return;możesz powiedzieć assert falsei dodać -ea OR -ea package OR -ea classNamedo argumentów jvm. Zaletą jest to, że pozwala to na pewną szczegółowość i wymaga dodania dodatkowego parametru do wywołania jvm, więc nie ma potrzeby ustawiania flagi DEBUG w kodzie, ale przez dodanie argumentu w czasie wykonywania, co jest przydatne, gdy cel nie jest programista, a rekompilacja i transfer kodu bajtowego zajmuje trochę czasu.

Jest też System.exit(0)sposób, ale może to być przesada, jeśli umieścisz go w Javie na stronie JSP, spowoduje to zamknięcie serwera.

Poza tym Java jest z założenia językiem „niania”, wolałbym raczej użyć czegoś natywnego, takiego jak C / C ++, aby uzyskać większą kontrolę.

Michael S.
źródło
Zmiana czegoś ze static finalzmiennej, która jest ustawiona w statycznym bloku inicjalizacji, na static finalzmienną, która jest ustawiona w jej deklaracji, nie powinna być istotną zmianą. Jest całkowicie prawdopodobne, że kiedy klasa jest zapisywana po raz pierwszy, może czasami nie być w stanie czegoś zrobić (i nie wiadomo do czasu wykonania, czy będzie w stanie to zrobić, czy nie), ale późniejsze wersje klasy mogły być w stanie rób to zawsze. Zmiana myClass.canFoona stałą powinna być if (myClass.canFoo) myClass.Foo(); else doSomethingElse();bardziej wydajna - a nie przerywać.
supercat