Почему в Java возникает ошибка компилятора «недостижимый оператор»?

84

Я часто обнаруживаю, что при отладке программы удобно (хотя, возможно, и плохая практика) вставлять оператор возврата внутри блока кода. Я мог бы попробовать что-то подобное на Java ....

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

конечно, это приведет к ошибке компилятора.

Test.java:7: недостижимый оператор

Я мог понять, почему предупреждение может быть оправдано, поскольку неиспользованный код - плохая практика. Но я не понимаю, почему это должно вызывать ошибку.

Это просто Java пытается быть няней, или есть веская причина сделать это ошибкой компилятора?

Майк
источник
11
Java также не совсем согласен с этим. Для некоторых потоков управления, вызывающих мертвый код, но Java не жалуется. Для других это так. Определить, какой код мертв - проблема, которую невозможно решить. Я не знаю, почему Java решила начать то, чего не удалось закончить.
Paul Draper

Ответы:

64

Потому что недостижимый код не имеет смысла для компилятора. Хотя сделать код значимым для людей и первостепенно и сложнее, чем сделать его значимым для компилятора, компилятор является основным потребителем кода. Разработчики Java придерживаются точки зрения, что код, не имеющий смысла для компилятора, является ошибкой. Их позиция состоит в том, что если у вас есть недостижимый код, вы сделали ошибку, которую необходимо исправить.

Здесь есть аналогичный вопрос: Недостижимый код: ошибка или предупреждение? , в котором автор говорит: «Лично я считаю, что это должна быть ошибка: если программист пишет фрагмент кода, он всегда должен иметь намерение фактически запустить его в каком-либо сценарии». Очевидно, что разработчики языка Java согласны.

Должен ли недостижимый код предотвращать компиляцию - это вопрос, по которому никогда не будет консенсуса. Но именно поэтому разработчики Java сделали это.


Ряд людей в комментариях указывают на то, что существует множество классов недостижимого кода, который Java не препятствует компиляции. Если я правильно понимаю последствия Геделя, ни один компилятор не сможет поймать все классы недостижимого кода.

Модульные тесты не могут отловить каждую ошибку. Мы не используем это как аргумент против их ценности. Точно так же компилятор не может уловить весь проблемный код, но он по-прежнему имеет ценность для предотвращения компиляции плохого кода, когда это возможно.

Разработчики языка Java считают недоступный код ошибкой. Поэтому разумно предотвращать его компиляцию, когда это возможно.


(Прежде чем вы проголосуете против: вопрос не в том, должна ли Java иметь ошибку компилятора недостижимых операторов. Вопрос в том, почему в Java есть ошибка компилятора недостижимых операторов. Не опускайте меня только потому, что вы думаете, что Java приняла неправильное проектное решение.)

СэмСтивенс
источник
22
Очевидно, что разработчики языка Java согласны с тем, что «разработчики языка Java» тоже люди, склонные к ошибкам. Лично я считаю, что у другой стороны обсуждаемого вами обсуждения есть гораздо более веские аргументы.
Никита Рыбак
6
@Gabe: Потому что это небезобидно - это почти наверняка ошибка. Либо вы поместили свой код в неправильное место, либо неправильно поняли, что написали свое утверждение таким образом, что некоторые из них недоступны. Создание этой ошибки предотвращает запись неправильного кода, и если вы хотите, чтобы что-то в недоступном месте вашего кода было прочитано другими разработчиками (единственная аудитория для недоступного кода), используйте вместо этого комментарий.
SamStephens,
10
SamStephens: Видя, что невызванные методы и неиспользуемые переменные, по сути, являются формами недостижимого кода. Почему разрешить одни формы, а другие нет? В частности, зачем запрещать такой полезный механизм отладки?
Гейб
5
Разве комментарии тоже недоступны? На мой взгляд, недостижимый код на самом деле является формой комментариев (это то, что я пытался сделать ранее и т. Д.). Но учитывая, что «настоящие» комментарии тоже не «достижимы» ... может быть, им тоже стоит поднять эту ошибку;)
Брэди Мориц
10
Проблема в том, что они (разработчики языка java) не согласны с этим. Если есть реальный анализ потока, тривиально понять, что оба return; System.out.println();и if(true){ return; } System.out.println();гарантированно будут иметь одинаковое поведение, но одно из них является ошибкой компиляции, а другое - нет. Итак, когда я отлаживаю и использую этот метод для временного «комментирования» кода, я это делаю именно так. Проблема с закомментированием кода заключается в том, что вам нужно найти подходящее место, чтобы остановить свой комментарий, что может занять больше времени, чем очень return;быстрое добавление .
LadyCailin,
47

Нет однозначной причины, по которой нельзя допускать недостижимые утверждения; другие языки позволяют их без проблем. Для вашей конкретной потребности это обычный трюк:

if (true) return;

Это выглядит бессмысленно: любой, кто прочитает код, догадается, что это должно быть сделано намеренно, а не из-за небрежной ошибки, когда остальные операторы остаются недоступными.

В Java есть небольшая поддержка «условной компиляции».

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

if (false) { x=3; }

не приводит к ошибке времени компиляции. Оптимизирующий компилятор может понять, что оператор x = 3; никогда не будет выполняться и может опустить код для этого оператора из сгенерированного файла класса, но оператор x = 3; не считается «недоступным» в техническом смысле, указанном здесь.

Обоснование такого различия в обращении состоит в том, чтобы позволить программистам определять «флаговые переменные», такие как:

static final boolean DEBUG = false;

а затем напишите такой код:

if (DEBUG) { x=3; }

Идея заключается в том, чтобы можно было изменить значение DEBUG с false на true или с true на false, а затем правильно скомпилировать код без каких-либо других изменений в тексте программы.

бесспорный
источник
2
До сих пор не понимаю, зачем тебе показывать трюк. Недостижимый код не имеет смысла для компилятора. Так что единственная аудитория для него - разработчики. Используйте комментарий. Хотя я предполагаю, что если вы временно добавляете возврат, использование вашего обходного пути может быть немного быстрее, чем просто вставка возврата и комментирование следующего кода.
SamStephens,
8
@SamStephens. Но каждый раз, когда вы хотели переключиться, вам нужно было помнить все места, где вы должны закомментировать код, а где вы должны сделать наоборот. Вам придется прочитать весь файл, изменяя исходный код, возможно, время от времени делая некоторые тонкие ошибки, вместо того, чтобы просто заменять «истина» на «ложь» в одном месте. Думаю, лучше один раз закодировать логику переключения и не трогать ее без необходимости.
Роберт
«любой, кто прочитает код, догадается, что это должно быть сделано намеренно». На мой взгляд, в этом и заключается проблема, люди не должны гадать, они должны понимать. Запоминание кода - это общение с людьми, а не просто инструкции для компьютеров. Если это что-то иное, чем исключительно временное, на мой взгляд, вам следует использовать конфигурацию. Взгляд на то, что вы показываете выше, if (true) return;не указывает на то, ПОЧЕМУ вы пропускаете логику, а if (BEHAVIOURDISABLED) return;выражает намерение.
SamStephens,
5
Этот трюк действительно полезен для отладки. Я часто добавляю возврат if (true), чтобы исключить «остальную часть» метода, пытающегося выяснить, где он терпит неудачу. Есть и другие способы, но они чистые и быстрые - но это не то, что вам когда-либо следует проверять.
Билл К.
19

Это няня. Я чувствую, что .Net понял это правильно - он выдает предупреждение о недоступности кода, но не ошибку. Хорошо быть предупрежденным об этом, но я не вижу причин предотвращать компиляцию (особенно во время сеансов отладки, когда неплохо сделать возврат, чтобы обойти некоторый код).

Брэди Мориц
источник
1
java была разработана раньше, в то время счетчик каждого байта, гибкие диски все еще были высокотехнологичными.
непререкаемый
1
Для раннего возврата отладки потребуется еще пять секунд, чтобы закомментировать строки, следующие за вашим ранним возвратом. Небольшая плата за ошибки, которые можно предотвратить, сделав недоступный код ошибкой.
SamStephens,
6
компилятор также может легко исключить недоступный код. нет причин заставлять разработчика делать это.
Брэди Мориц
2
@SamStephens нет, если у вас там уже есть комментарии, поскольку комментарии не складываются, не нужно обойти это с помощью комментариев.
enrey
@SamStephens Закомментированный код плохо рефакторинг.
cowlinator
14

Я только что заметил этот вопрос и хотел добавить к нему свои 0,02 доллара.

В случае Java это вообще не вариант. Ошибка «недоступного кода» возникает не из-за того, что разработчики JVM думали защитить разработчиков от чего-либо или проявлять особую бдительность, а из требований спецификации JVM.

И компилятор Java, и JVM используют так называемые «карты стека» - определенную информацию обо всех элементах в стеке, выделенных для текущего метода. Тип каждого слота стека должен быть известен, чтобы инструкция JVM не использовала элемент одного типа для другого. Это в основном важно для предотвращения использования числового значения в качестве указателя. Возможно, используя сборку Java, попытаться отправить / сохранить число, но затем вывести / загрузить ссылку на объект. Однако JVM отклонит этот код во время проверки класса, то есть когда карты стека создаются и проверяются на согласованность.

Чтобы проверить карты стека, виртуальная машина должна пройти через все пути кода, существующие в методе, и убедиться, что независимо от того, какой путь кода будет когда-либо выполняться, данные стека для каждой инструкции соответствуют тому, что было отправлено любым предыдущим кодом. / хранится в стеке. Итак, в простом случае:

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

в строке 3 JVM проверит, что обе ветви if были сохранены только в (который является просто локальным var # 0) что-то, что совместимо с Object (поскольку именно так код из строки 3 и далее будет обрабатывать локальную var # 0 ).

Когда компилятор доходит до недостижимого кода, он не совсем знает, в каком состоянии стек может быть в этот момент, поэтому он не может проверить его состояние. На этом этапе он больше не может полностью компилировать код, так как он также не может отслеживать локальные переменные, поэтому вместо того, чтобы оставить эту неоднозначность в файле класса, он выдает фатальную ошибку.

Конечно, простое условие вроде if (1<2) обманет его, но на самом деле это не обман - оно дает ему потенциальную ветвь, которая может привести к коду, и, по крайней мере, и компилятор, и виртуальная машина могут определить, как элементы стека могут быть использованы оттуда. на.

PS Я не знаю, что делает .NET в этом случае, но я считаю, что компиляция тоже не удастся. Обычно это не проблема для компиляторов машинного кода (C, C ++, Obj-C и т. Д.).

Павел Веселов
источник
Насколько агрессивна система предупреждения о мертвом коде Java? Может ли это быть выражено как часть грамматики (например, { [flowingstatement;]* }=> flowingstatement, { [flowingstatement;]* stoppingstatement; }=> stoppingstatement, return=> stoppingstatement, if (condition) stoppingstatement; else stoppingstatement=> stoppingstatement;и т. Д.?
supercat
Я плохо разбираюсь в грамматике, но, думаю, да. Однако «остановка» может быть локальной, например оператором «break» внутри цикла. Кроме того, конец метода - это неявный оператор остановки для уровня метода, если метод недействителен. 'throw' также будет явным оператором остановки.
Павел Веселов
Что стандарт Java сказал бы о чем-то вроде того, что void blah() { try {return;} catch (RuntimeError ex) { } DoSomethingElse(); } Java закричит, что не существует способа выхода из tryблока через исключение, или он решит, что в любом tryблоке может произойти какое-либо непроверенное исключение ?
supercat
Лучший способ узнать это - попробовать скомпилировать это. Но компилятор допускает даже пустые операторы try / catch. Однако любая инструкция JVM теоретически может вызвать исключение, поэтому блок try может выйти из него. Но я не уверен, какое отношение это имеет к этому вопросу.
Павел Веселов
По сути, вопрос заключается в том, являются ли единственными запрещенными «недостижимыми» операторами те, которые могут быть определены как недоступные на основе синтаксического анализа, без необходимости фактически оценивать какие-либо выражения, или может ли стандарт запрещать такие недостижимые утверждения, как if (constantThatEqualsFive < 3) {doSomething;};.
supercat
5

Одна из целей компиляторов - исключить классы ошибок. Какой-то недостижимый код появляется случайно, приятно, что javac исключает этот класс ошибок во время компиляции.

Для каждого правила, обнаруживающего ошибочный код, кто-то захочет, чтобы компилятор принял его, потому что они знают, что делают. Это наказание за проверку компилятора, и получение правильного баланса - одна из хитрых точек дизайна языка. Даже при строжайшей проверке можно написать бесконечное количество программ, так что все не может быть так плохо.

Рики Кларксон
источник
5

Хотя я считаю, что эта ошибка компилятора - это хорошо, есть способ ее обойти. Используйте условие, которое, как вы знаете, будет истинным:

public void myMethod(){

    someCodeHere();

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

    moreCodeHere();

}

Компилятор недостаточно умен, чтобы жаловаться на это.

Шон Патрик Флойд
источник
6
Вопрос в том, почему? Недостижимый код не имеет смысла для компилятора. Так что единственная аудитория для него - разработчики. Используйте комментарий.
SamStephens,
3
Итак, я получаю отрицательные голоса, потому что согласен с тем, как разработчики Java разработали свою компиляцию? Боже ...
Шон Патрик Флойд
1
Вы получаете отрицательные голоса, если не отвечаете на вопрос. См. Комментарий Сэма Стивенса.
BoltClock
2
javac может определенно сделать вывод, что 1<2это правда, а остальную часть метода не нужно включать в байтовый код. правила «недостижимых операторов» должны быть четкими, фиксированными и независимыми от сообразительности компиляторов.
безупречный
1
@Sam с таким отношением, вам придется проголосовать против 50% лучших ответов этого сайта (и я не говорю, что мой ответ входит в их число). Большая часть ответов на вопросы по SO, как и в любой ситуации ИТ-консалтинга, состоит в том, чтобы определить реальный вопрос (или соответствующие побочные вопросы) из данного вопроса.
Шон Патрик Флойд,
0

Конечно, хорошо жаловаться, что чем строже компилятор, тем лучше, поскольку он позволяет вам делать то, что вам нужно. Обычно небольшая цена - это закомментировать код, а выигрыш в том, что когда вы компилируете код, он работает. Общий пример - Haskell, о котором люди кричат ​​до тех пор, пока не поймут, что их тестирование / отладка - это только основной и короткий тест. Лично я в Java почти не занимаюсь отладкой, будучи (фактически, специально) невнимательным.

Жером ЖАН-ЧАРЛЬЗ
источник
0

Если причиной для разрешения if (aBooleanVariable) return; someMoreCode;является разрешение флагов, то тот факт, что if (true) return; someMoreCode;не генерирует ошибку времени компиляции, кажется несогласованностью в политике генерации исключения CodeNotReachable, поскольку компилятор `` знает '', чтоtrue это не флаг (не переменная).

Два других способа, которые могут быть интересны, но не относятся к отключению части кода метода, а также if (true) return :

Теперь вместо того, чтобы говорить, if (true) return;вы можете сказать assert falseи добавить-ea OR -ea package OR -ea className к аргументам jvm. Хорошим моментом является то, что это обеспечивает некоторую степень детализации и требует добавления дополнительного параметра к вызову jvm, поэтому нет необходимости устанавливать флаг DEBUG в коде, а только путем добавления аргумента во время выполнения, что полезно, когда целью не является машина разработчика и перекомпиляция и передача байт-кода требует времени.

Есть также System.exit(0)способ, но это может быть излишним, если вы поместите его в Java в JSP, он завершит работу сервера.

Помимо того, что Java изначально является языком «няни», я бы предпочел использовать что-то родное, например C / C ++, для большего контроля.

MichaelS
источник
Изменение чего-либо из static finalпеременной, которая установлена ​​в статическом блоке инициализации, на static finalпеременную, которая установлена ​​в ее объявлении, не должно быть критическим изменением. Вполне вероятно, что при первом написании класса он мог иногда быть неспособным что-то сделать (и до времени выполнения не было известно, сможет ли он это сделать или нет), но более поздние версии класса могли быть в состоянии делайте это действие всегда. Переход myClass.canFooна константу должен сделать его if (myClass.canFoo) myClass.Foo(); else doSomethingElse();более эффективным, а не нарушить его.
supercat