Почему у Java есть ошибка компилятора "недостижимое утверждение"?

Я часто нахожу, что при отладке программы удобно (хотя это, возможно, и плохая практика) вставить оператор возврата в блок кода. Я мог бы попробовать что-то подобное в 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 пытается быть няней, или есть веская причина сделать это ошибкой компилятора?

8 ответов

Решение

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

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

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


Многие люди в комментариях отмечают, что существует множество классов недоступного кода, которые Java не мешает компилировать. Если я правильно понимаю последствия Gödel, никакой компилятор не сможет перехватить все классы недоступного кода.

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

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


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

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

if (true) return;

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

Java немного поддерживает "условную компиляцию"

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

if (false) { x=3; }

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

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

static final boolean DEBUG = false;

а затем написать код, такой как:

if (DEBUG) { x=3; }

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

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

Я только заметил этот вопрос и хотел добавить свои $.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' сохранили только в (который является просто локальной переменной #0) что-то, совместимое с Object (так как код из строки 3 и далее будет обрабатывать локальную переменную #0).).

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

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

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

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

public void myMethod(){

    someCodeHere();

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

    moreCodeHere();

}

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

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

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

Если причина для разрешения 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++, для большего контроля.

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

Другие вопросы по тегам