Как компиляторы должны сообщать об ошибках и предупреждениях?

11

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

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

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

C # не имеет таких же проблем, но у него есть несколько. Кажется, что компиляция происходит в несколько проходов, и сбой прохода будет препятствовать выполнению дальнейших проходов. Из-за этого количество ошибок, которое вы получаете при сбое сборки, часто сильно недооценивается. При одном запуске может появиться две ошибки, но как только вы исправите их, возможно, вы получите 26 новых.

Переход на C и C ++ просто показывает плохую комбинацию диагностических недостатков компиляции в Java и C # (хотя было бы точнее сказать, что Java и C # просто пошли своим путем с половиной проблем каждый). Некоторые предупреждения действительно должны быть ошибками (например, когда не все пути кода возвращают значение), и все же они являются предупреждениями, потому что, я полагаю, в то время, когда они писали стандарт, технология компилятора не была достаточно хороша, чтобы делать подобные проверки обязательны. В том же духе, компиляторы часто проверяют больше, чем говорит стандарт, но все же используют «стандартный» уровень ошибки предупреждения для дополнительных выводов. И часто компиляторы не сообщают обо всех ошибках, которые они могут найти сразу; может потребоваться несколько компиляций, чтобы избавиться от всех из них. Не говоря уже о загадочных ошибках, которые любят компиляторы C ++,

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

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

PHP, со своей стороны, имеет кучу более или менее значительных уровней ошибок и исключений. Об ошибках разбора сообщается по одному, предупреждения часто настолько плохи, что должны прервать ваш скрипт (но не по умолчанию), уведомления действительно часто показывают серьезные проблемы с логикой, некоторые ошибки на самом деле не настолько плохи, чтобы остановить ваш скрипт, но все же и, как обычно, с PHP, есть некоторые действительно странные вещи (почему, черт возьми, нам нужен уровень ошибок для фатальных ошибок, которые на самом деле не фатальны?, E_RECOVERABLE_E_ERRORя говорю с вами).

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

Как вы думаете, должен быть правильный способ сообщить об ошибках компилятора?

zneak
источник
-1: «Некомпилированные языки все еще имеют свою долю дерьмовых сообщений об ошибках» Субъективные и аргументированные. Действительно бесполезно. Это вопрос или жалоба?
С.Лотт
2
@ S.Lott Я думаю, ты немного на грани. Я обнаружил, что мне гораздо сложнее работать с компилируемыми языками, и это не беспокоило вас.
zneak
@zneak: другие утверждения ближе к фактическим, и их сложнее разобрать. Это утверждение было легче всего продемонстрировать как субъективное и аргументированное.
S.Lott
1
@ S.Lott Неправильно ли я заявляю, что Python указывает одну ошибку за раз?
zneak
1
@ S.Lott Тогда все должно было измениться, потому что в прошлый раз, когда я пытался, любая синтаксическая ошибка приводила к тому, что Python прекращал пытаться «скомпилировать», а ошибка имени приводила к исключению и не проверяла остальную часть функции (хотя это оставляло место для сообщения об одной ошибке на тестируемую единицу). Мое субъективное и аргументированное утверждение было введением в то, что я считал фактом, но если это больше не так, я пойду и отредактирую свой вопрос. Как это работает сейчас?
zneak

Ответы:

6

Похоже, ваш вопрос на самом деле не о том, как мы сообщаем об ошибках компилятора, а о классификации проблем и о том, что с ними делать.

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

  1. Разные «уровни» предупреждения. Многие компиляторы как-то реализуют это (например, GCC имеет много переключателей для точной настройки того, о чем он будет предупреждать), но это требует работы - например, сообщения о серьезности сообщаемого предупреждения и возможности устанавливать предупреждения ошибки "только для предупреждений выше указанной серьезности.

  2. Разумная классификация ошибок и предупреждений. Об ошибке следует сообщать только в том случае, если код не соответствует спецификации и, следовательно, не может быть скомпилирован. Недоступные операторы, хотя, вероятно, и являются ошибкой кодирования, должны быть предупреждением , а не ошибкой - код все еще «действителен», и существуют допустимые случаи, в которых нужно компилировать с недоступным кодом (например, быстрые изменения для отладки) ,

Теперь о чем я не согласен с вами:

  1. Прилагать дополнительные усилия, чтобы сообщить о каждой проблеме. Если есть ошибка, это нарушает сборку. Сборка сломана. Сборка не будет работать, пока эта ошибка не будет исправлена. Следовательно, лучше сразу сообщать об этой ошибке, чем «продолжать», чтобы попытаться идентифицировать все остальное «не так» с кодом. Особенно, когда многие из этих вещей, вероятно, вызваны первоначальной ошибкой.

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

Мысли?

Anon.
источник
Я согласен с вами, за исключением моментов, в которых мы не согласны (дух), так что это +1 от меня. Я думаю, что достаточно просто заставить каждый путь кода либо возвращать значение, либо прерывать вашу программу, учитывая, насколько это плохо, когда вы на самом деле попадаете в случае неопределенного поведения.
zneak
7

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

Это (в значительной степени) компромисс со стороны автора компилятора. В зависимости от того, какая ошибка вы сделали, это очень легко для компилятора , чтобы начать неправильно понимать , что вы делаете достаточно плохо , что он начинает ошибок отчетов , которые имеют очень мало общего с реальностью. Например, рассмотрим простую опечатку, в которой itn x;вместо чего-то есть int x;. Если вы не сделали что-то еще, что может что-то itnзначить, это будет сообщено как ошибка. Это хорошо, насколько это возможно, но теперь посмотрим, что произойдет дальше - компилятор смотрит на большое количество кода, который пытается использовать x в качестве переменной. Должен ли он A) остановиться и позволить вам это исправить, или B) извергнуть 2000 ошибок error: "x": undeclared identifierили что-то в этом порядке? Рассмотрим другую возможность:

int main()[

Это еще одна довольно очевидная опечатка - очевидно, она должна быть {вместо а [. Компилятор может довольно легко сказать вам эту часть - но должен ли он затем сообщать об ошибке, например, x=1;что-то вроде error: statement only allowed inside a function?

Обратите внимание, что это даже довольно тривиальные проблемы - их гораздо легче найти (особенно, как большинство из нас знает, когда вы переходите на шаблоны C ++). Суть в том, что создатель компилятора обычно застревает в попытках найти компромисс между сообщением о ложных ошибках (т. Е. Сообщением о чем-либо как об ошибке, даже если это нормально) и невозможностью сообщить о реальных ошибках. Есть несколько практических правил, которым следует следовать, чтобы не пойти слишком далеко в обоих направлениях, но почти ни одно из них не близко к идеальному.

Еще одна проблема, которую вы упомянули, была Java и @SupressWarning. Это очень отличается от вышеупомянутого - это было бы довольно тривиально исправить. Единственная причина, по которой это не исправлено, заключается в том, что это не соответствует базовому «характеру» Java - то есть, по их мнению, «это не ошибка, а особенность». Несмотря на то, что обычно это шутка, в этом случае вовлеченные люди настолько заблуждаются, что действительно верят, что это правда.

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

Джерри Гроб
источник
3
В дополнение к вашему замечанию о ранних ошибках, вызывающих многие другие, есть также тот факт, что более поздние проходы часто строятся так, чтобы требовать, чтобы более ранние проходы были завершены успешно. Например, один из ранних проходов в компиляторе C # проверяет, что в графе наследования нет циклов - у вас нет наследования A от B, которое наследуется от A. Если вы хотите продолжить и сгенерировать список из всех ошибок после этого каждый последующий проход должен справляться с циклами, что значительно замедляет работу даже на «хороших» компиляциях.
Анон.
@Anon. Компилятор Java прилагает гораздо больше усилий для выживания на ранних этапах, и я не нахожу его значительно медленнее. Для меня это несколько раздражает, как быстро cscсдается.
zneak
@zneak: Как говорит Джерри, это компромисс со стороны разработчиков компиляторов. Написание хорошей диагностики ошибок на самом деле является очень сложной проблемой (посмотрите на Clang пример того, как далеко вы действительно можете это сделать). Смотрите здесь для хорошего обсуждения этапов и этапов компилятора C #.
Дин Хардинг