W jaki sposób kompilatory powinny zgłaszać błędy i ostrzeżenia?

11

Nie planuję pisać kompilatora w najbliższej przyszłości; nadal jestem dość zainteresowany technologiami kompilatora i tym, jak można to ulepszyć.

Zaczynając od języków skompilowanych, większość kompilatorów ma dwa poziomy błędów: ostrzeżenia i błędy, z których pierwszy to najczęściej niekrytyczne rzeczy, które należy naprawić, oraz błędy wskazujące przez większość czasu, że niemożliwe jest wygenerowanie maszyny (lub bajtu) kod z wejścia.

Chociaż jest to dość słaba definicja. W niektórych językach, takich jak Java, niektóre ostrzeżenia są po prostu niemożliwe do usunięcia bez zastosowania @SuppressWarningdyrektywy. Ponadto Java traktuje niektóre niekrytyczne problemy jako błędy (na przykład nieosiągalny kod w Javie powoduje błąd z powodu, który chciałbym wiedzieć).

C # nie ma takich samych problemów, ale ma kilka. Wygląda na to, że kompilacja zachodzi w kilku przebiegach, a niepowodzenie przejścia uniemożliwi dalsze wykonywanie. Z tego powodu liczba błędów pojawiających się w przypadku niepowodzenia kompilacji jest często rażąco niedoceniana. Przy jednym uruchomieniu może to oznaczać, że masz dwa błędy, ale kiedy je naprawisz, może otrzymasz 26 nowych.

Kopanie w C i C ++ po prostu pokazuje złą kombinację słabości diagnostycznych kompilacji Java i C # (choć może bardziej trafne jest stwierdzenie, że Java i C # po prostu poszły z każdą połową problemów). Niektóre ostrzeżenia naprawdę powinny być błędami (na przykład, gdy nie wszystkie ścieżki kodu zwracają wartość), a mimo to są ostrzeżeniami, ponieważ, jak sądzę, w czasie, gdy pisali standard, technologia kompilatora nie była wystarczająco dobra, aby tworzyć tego rodzaju kontrole obowiązkowe. W tym samym duchu kompilatory często sprawdzają więcej niż mówi standard, ale nadal używają „standardowego” poziomu błędu ostrzegawczego dla dodatkowych ustaleń. I często kompilatory nie zgłaszają od razu wszystkich błędów, które mogą znaleźć; pozbycie się wszystkich może zająć kilka kompilacji. Nie wspominając o tajemniczych błędach, które kompilatory C ++ lubią pluć,

Dodając teraz, że wiele systemów kompilacji można konfigurować w celu zgłaszania awarii, gdy kompilatory emitują ostrzeżenia, otrzymujemy dziwną mieszankę: nie wszystkie błędy są krytyczne, ale niektóre ostrzeżenia powinny; nie wszystkie ostrzeżenia są zasłużone, ale niektóre są wyraźnie tłumione bez dalszej wzmianki o ich istnieniu; a czasami wszystkie ostrzeżenia stają się błędami.

Języki nieskompilowane nadal mają nieprzyjemny raport błędów. Literówki w Pythonie nie będą zgłaszane, dopóki kod nie zostanie faktycznie uruchomiony, i tak naprawdę nigdy nie można wykopać więcej niż jednego błędu naraz, ponieważ skrypt przestanie działać po tym, jak go napotka.

Z drugiej strony PHP ma kilka mniej lub bardziej znaczących poziomów błędów i wyjątków. Błędy analizy są zgłaszane pojedynczo, ostrzeżenia są często tak złe, że powinny przerwać skrypt (ale nie domyślnie), powiadomienia naprawdę często pokazują poważne problemy logiczne, niektóre błędy naprawdę nie są wystarczająco złe, aby zatrzymać skrypt, ale nadal tak, jak zwykle w przypadku PHP, jest tam kilka naprawdę dziwnych rzeczy (dlaczego, do cholery, potrzebujemy poziomu błędu dla błędów krytycznych, które nie są tak naprawdę fatalne ? E_RECOVERABLE_E_ERROR, mówię do ciebie).

Wydaje mi się, że każda implementacja raportowania błędów kompilatora, o której myślę, jest zepsuta. To wielka szkoda, ponieważ wszyscy dobrzy programiści nalegają na to, jak ważne jest prawidłowe radzenie sobie z błędami, a jednocześnie nie mogą zdobyć własnych narzędzi.

Jak myślisz, jaki powinien być właściwy sposób zgłaszania błędów kompilatora?

skradać się
źródło
-1: „Języki nieskompilowane nadal mają uciążliwy raport o błędach” Subiektywne i kłótliwe. Naprawdę nieprzydatne. Czy to pytanie czy skarga?
S.Lott,
2
@ S.Lott Myślę, że jesteś trochę na krawędzi. Uważam, że trudniej mi było pisać skompilowane języki i nie przeszkadzało mi to.
zneak 25.01.11
@zneak: Inne stwierdzenia są bliższe byciu faktami i trudniejsze do przeanalizowania. To stwierdzenie najłatwiej wykazać jako subiektywne i kłótliwe.
S.Lott,
1
@ S.Lott Czy mylę się, twierdząc, że Python wskazuje jeden błąd na raz?
zneak
1
@ S.Lott Wtedy wszystko musiało się zmienić, ponieważ przy ostatniej próbie każdy błąd składniowy spowodowałby, że Python przestałby próbować „skompilować”, a błąd nazwy rzuciłby wyjątek i nie sprawdził reszty funkcji (chociaż to pozostawiło miejsce na zgłoszenie jednego błędu na jednostkę testowalną). Moje subiektywne i kłótliwe stwierdzenie było wstępem do tego, co uważałem za fakt, ale jeśli to już nie jest prawda, przejdę do edycji pytania. Jak to teraz działa?
zneak

Odpowiedzi:

6

Twoje pytanie nie wydaje się dotyczyć tego , w jaki sposób zgłaszamy błędy kompilatora - chodzi raczej o klasyfikację problemów i co z nimi zrobić.

Jeśli zaczniemy od założenia, że ​​dychotomia ostrzeżenie / błąd jest poprawna, zobaczmy, jak dobrze możemy na tym opierać się. Jakieś pomysły:

  1. Różne „poziomy” ostrzeżenia. Wiele kompilatorów sortuje to zaimplementować (na przykład GCC ma wiele przełączników do konfigurowania dokładnie tego, o czym będzie ostrzegał), ale potrzebuje pracy - na przykład, raportowania o tym, jak poważne jest zgłaszane ostrzeżenie i możliwości ustawiania „ostrzeżeń” są błędami ”tylko w przypadku ostrzeżeń powyżej określonego poziomu ważności.

  2. Rozsądna klasyfikacja błędów i ostrzeżeń. Błąd należy zgłaszać tylko wtedy, gdy kod nie spełnia specyfikacji, a zatem nie można go skompilować. Nieosiągalne instrukcje, choć prawdopodobnie błąd kodowania, powinny być ostrzeżeniem , a nie błędem - kod jest nadal „poprawny”, a istnieją uzasadnione przypadki, w których można by skompilować z nieosiągalnym kodem (na przykład szybkie modyfikacje do debugowania) .

Teraz rzeczy, z którymi się nie zgadzam:

  1. Dokładamy wszelkich starań, aby zgłosić każdy problem. Jeśli wystąpi błąd, psuje kompilację. Kompilacja jest zepsuta. Kompilacja nie będzie działać, dopóki ten błąd nie zostanie naprawiony. Dlatego lepiej jest natychmiast zgłosić ten błąd, niż „kontynuować”, aby spróbować zidentyfikować wszystko „źle” za pomocą kodu. Zwłaszcza, gdy wiele z tych rzeczy jest prawdopodobnie spowodowanych początkowym błędem.

  2. Twój konkretny przykład ostrzeżenia, które powinno być błędem. Tak, to prawdopodobnie błąd programisty. Nie, nie powinno to zepsuć kompilacji. Jeśli wiem, że dane wejściowe do funkcji zawsze zwracają wartość, powinienem móc uruchomić kompilację i wykonać kilka testów bez konieczności dodawania tych dodatkowych kontroli. Tak, powinno to być ostrzeżenie. I to cholernie poważne. Ale nie powinno to samo z siebie przerywać kompilacji, chyba że kompiluje się z ostrzeżeniami-są-błędami.

Myśli?

Zaraz.
źródło
Zgadzam się z tobą, z wyjątkiem punktów, w których się nie zgadzamy (duh), więc to +1 ode mnie. Myślę, że łatwo jest sprawić, aby każda ścieżka kodu albo zwróciła wartość, albo przerwała program, biorąc pod uwagę, jak źle jest, kiedy faktycznie wpadasz w niezdefiniowane zachowanie.
zneak
7

Jednym z poruszonych przez ciebie problemów było niekompletne zgłaszanie błędów - np. Zgłoszenie 2 błędów, a kiedy je naprawisz, dostaniesz więcej.

Jest to (w dużej mierze) kompromis ze strony autora kompilatora. W zależności od tego, jaki błąd popełniłeś, kompilatorowi bardzo łatwo jest źle zrozumieć, co robisz , na tyle, że zaczyna zgłaszać błędy, które mają niewiele wspólnego z rzeczywistością. Na przykład rozważ prostą literówkę, w której itn x;zamiast czegoś masz coś takiego int x;. Jeśli nie zrobiłeś czegoś innego, co sprawia, że itncoś znaczy, to zostanie to zgłoszone jako błąd. Na razie jest to w porządku, ale teraz zastanów się, co będzie dalej - kompilator patrzy na wiele kodów, które próbują użyć x jako zmiennej. Czy A) powinien przestać i pozwolić Ci to naprawić, lub B) wyrzucić 2000 błędów error: "x": undeclared identifierlub coś w tej kolejności? Rozważ inną możliwość:

int main()[

To kolejna dość oczywista literówka - oczywiście powinna to być {zamiast [. Kompilator może dość łatwo powiedzieć ci tę część - ale czy powinien następnie zgłosić błąd w przypadku x=1;powiedzenia czegoś takiego error: statement only allowed inside a function?

Zauważ, że są to nawet dość trywialne problemy - znacznie gorsze są łatwe do znalezienia (zwłaszcza, jak większość z nas wie, kiedy wchodzisz w szablony C ++). Najważniejsze jest to, że pisarz kompilatora zwykle utknął w próbie kompromisu między zgłaszaniem fałszywych błędów (tj. Zgłaszaniem czegoś jako błędu, nawet jeśli jest w porządku) a nie zgłaszaniem prawdziwych błędów. Istnieją pewne podstawowe zasady, których należy przestrzegać, aby uniknąć zbytniego popełniania błędów w obu kierunkach, ale prawie żadna z nich nie jest bliska ideału.

Innym problemem, o którym wspominałeś był Java i @SupressWarning. Jest to zupełnie inne od powyższego - naprawienie tego byłoby dość proste. Jedynym powodem, dla którego nie zostało to naprawione, jest to, że nie pasuje to do podstawowego „charakteru” Javy - tzn. Ich zdaniem „to nie jest błąd, to funkcja”. Mimo że zwykle jest to żart, w tym przypadku ludzie są tak wprowadzeni w błąd, że naprawdę wierzą, że to prawda.

Problem, o którym wspominasz w C i C ++ ze ścieżkami kodu, które nie zwracają wartości, tak naprawdę nie pozwala na proste kompilatory. Pozwala to na dekady istnienia kodu , którego część nikt nie chce naprawić, dotknąć, a nawet przeczytać. Jest pradawny i brzydki, ale działa, i nikt nie chce niczego poza tym, aby mógł dalej działać. Na dobre lub złe, komitety językowe są mocno utkwione w utrzymywaniu wstecznej kompatybilności, więc nadal dopuszczają rzeczy, których nikt tak naprawdę nie lubi - ale niektórzy ludzie (przynajmniej myślą, że potrzebują).

Jerry Coffin
źródło
3
Oprócz twojego stwierdzenia o wczesnych błędach powodujących wiele innych, istnieje również fakt, że późniejsze podania są często budowane tak, aby wcześniejsze zaliczenia zakończyły się powodzeniem. Na przykład jeden z pierwszych przebiegów kompilatora C # sprawdza, czy nie ma cykli na wykresie dziedziczenia - nie masz dziedziczenia A z B, które dziedziczy po A. Jeśli chcesz kontynuować i wygenerować listę wszystkich późniejszych błędów, każde kolejne przejście musiałoby być w stanie poradzić sobie z cyklami - co znacznie go spowalnia, nawet przy „dobrych” kompilacjach.
Anon.
@Zaraz. Kompilator Java dokłada znacznie większych starań, aby przetrwać wczesne przejścia, i nie uważam, aby było to znacznie wolniejsze. Dla mnie trochę denerwujące jest to, jak szybko się cscpoddaje.
zneak 25.01.11
@zneak: Jak mówi Jerry, jest to kompromis ze strony twórców kompilatorów. Pisanie dobrych diagnostykę błędów jest rzeczywiście bardzo trudny problem (spojrzenie na brzękiem na przykład, jak daleko można się naprawdę wziąć go). Zobacz tutaj dobrą dyskusję na temat faz i przebiegów kompilatora C #.
Dean Harding