Як компілятори повинні повідомляти про помилки та попередження?


11

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

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

Хоча це досить слабке визначення. У деяких мовах, таких як Java, певних попереджень просто неможливо позбутися без використання @SuppressWarningдирективи. Крім того, Java трактує деякі не фатальні проблеми як помилки (наприклад, недоступний код на Java викликає помилку з причини, яку я хотів би знати).

У C # немає однакових проблем, але їх є кілька. Здається, що компіляція відбувається в декілька проходів, а відмова пропуску убереже подальші пропуски від виконання. Через це кількість помилок, які ви отримуєте, коли ваша збірка не вдається, часто буває заниженою. На одному запуску це може сказати, що у вас є дві помилки, але, як тільки ви виправите їх, можливо, ви отримаєте 26 нових.

Перекочування до C та C ++ просто показує погану комбінацію діагностичних слабкостей компіляції Java та C # (хоча, можливо, точніше сказати, що Java та C # просто пройшли шлях із половиною проблем). Деякі попередження дійсно повинні бути помилками (наприклад, коли не всі кодові шляхи повертають значення), і все ж вони попереджають, тому що, я думаю, в той час, коли вони писали стандарт, технологія компілятора була недостатньо хорошою для створення подібного роду чеки обов'язкові. У цьому ж ключі, компілятори часто перевіряють, чи не більше стандартного, але все ж використовують "стандартний" рівень помилок попередження для додаткових висновків. І часто компілятори не повідомлять про всі помилки, які вони могли б знайти відразу; для позбавлення від усіх може знадобитися кілька компіляцій. Не кажучи вже про критичні помилки компілятори C ++, які люблять плювати,

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

Нескладені мови все ще мають свою частку несанкціонованого повідомлення про помилки. Про помилки друку в Python не повідомляться, поки код фактично не запуститься, і ви ніколи не зможете виправити більше однієї помилки одночасно, оскільки сценарій припинить виконання після його зустрічі.

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

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

Як ви вважаєте, що має бути правильним способом повідомляти про помилки компілятора?


-1: "Нескладені мови все ще мають свою частку шаленого повідомлення про помилки" Суб'єктивне та аргументативне. Дійсно непомітний. Це питання чи скарга?
С.Лотт

2
@ S.Lott Я думаю, ти тут трохи на краю. Я вважаю, що мені було набагато важче в складанні мов, і це, здається, не турбувало вас.
zneak

@zneak: Інші твердження ближчі до того, що вони фактичні та складніші для розбору. Це твердження найлегше було показано суб'єктивним та аргументативним.
С.Лотт

1
@ S.Lott Чи помиляюсь, заявивши, що Python вказує на одну помилку?
zneak

1
@ S.Lott Тоді, мабуть, змінилося, тому що останній раз, коли я намагався, будь-яка синтаксична помилка призведе до того, що Python перестане намагатися «компілювати», і помилка імені викине виняток і не перевірить решту функції (хоча це все-таки залишилося приміщення для повідомлення про одну помилку на одиницю, що перевіряється). Моє суб’єктивне та аргументативне твердження було вступом до того, що я вважав фактом, але якщо це більше не відповідає дійсності, я піду редагувати своє запитання. Як це працює зараз?
zneak

Відповіді:


6

Здається, ваше запитання насправді не стосується того, як ми повідомляємо про помилки компілятора - скоріше, це стосується класифікації проблем та що з ними робити.

Якщо ми почнемо з припущення, на даний момент, що дихотомія попередження / помилки є правильною, давайте подивимося, наскільки добре ми можемо будувати поверх цього. Деякі ідеї:

  1. Різні "рівні" попередження. Багато компіляторів начебто реалізують це (наприклад, GCC має безліч комутаторів для налаштування саме того, про що вона буде попереджати), але йому потрібна робота - наприклад, повідомлення про суворість повідомлення про попередження та можливість встановлення "попереджень є помилками "лише для попереджень вище визначеної суворості.

  2. Розумна класифікація помилок та попереджень. Помилка повинна повідомлятися лише в тому випадку, якщо код не відповідає специфікації, а значить, не може бути скомпільований. Недоступні заяви, хоча, ймовірно, помилка кодування, повинні бути попередженням , а не помилкою - код все ще "дійсний", і є законні випадки, коли хотілося б компілювати з недоступним кодом (наприклад, швидкі зміни для налагодження) .

Тепер речі, з якими я не згоден:

  1. Доклавши додаткових зусиль, щоб повідомити про кожну проблему. Якщо є помилка, це порушує збірку. Збірка зламана. Збірка не працюватиме, поки ця помилка не буде виправлена. Отже, краще повідомити про цю помилку негайно, а не «продовжувати», щоб спробувати визначити все інше «неправильне» з кодом. Особливо, коли багато з цих речей, мабуть, викликані початковою помилкою все одно.

  2. Ваш конкретний приклад попередження, що повинно бути помилкою. Так, це, мабуть, помилка програміста. Ні, це не повинно порушувати збірку. Якщо я знаю, що вхід до функції такий, що він завжди повертатиме значення, я повинен мати змогу запустити збірку та зробити деякі тести, не потребуючи додавання цих додаткових перевірок. Так, це повинно бути попередженням. І чорт високої суворості. Але він не повинен порушувати складку сам по собі, якщо тільки компіляція з попередженнями не є помилками.

Думки?


Я згоден з вами, за винятком пунктів, де ми не погоджуємося (так), тож це +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 # перевіряє, чи немає циклів у графі спадкування - у вас немає спадщини від B, яка успадковується від A. Якщо ви хотіли продовжувати та генерувати список з усіх помилок після цього, кожен наступний прохід повинен був би мати можливість справлятися з циклами, що робить його значно повільнішим навіть при "хороших" компіляціях.
Анон.

@Anon. Компілятор Java докладає набагато більше зусиль, щоб пережити ранні переходи, і я не вважаю це значно повільніше. Мені дещо дратує те, як швидко cscздається.
zneak

@zneak: Як каже Джеррі, це є компромісом з боку розробників компіляторів. Написання гарної діагностики помилок насправді є дуже складною проблемою (подивіться на кланг для прикладу того, як далеко ви можете його реально взяти). Дивіться тут, щоб добре обговорити фази та проходи компілятора C #.
Дін Хардінг
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.