Wie sollten Compiler Fehler und Warnungen melden?

11

Ich habe nicht vor, in naher Zukunft einen Compiler zu schreiben. Trotzdem interessiere ich mich sehr für Compilertechnologien und wie dieses Zeug besser gemacht werden könnte.

Beginnend mit kompilierten Sprachen haben die meisten Compiler zwei Fehlerstufen: Warnungen und Fehler, wobei die erste meistens nicht schwerwiegende Probleme sind, die Sie beheben sollten, und Fehler, die meistens darauf hinweisen, dass es unmöglich ist, Maschinen- (oder Byte-) zu erzeugen. Code von der Eingabe.

Dies ist jedoch eine ziemlich schwache Definition. In einigen Sprachen wie Java können bestimmte Warnungen ohne Verwendung der @SuppressWarningDirektive einfach nicht entfernt werden . Außerdem behandelt Java bestimmte nicht schwerwiegende Probleme als Fehler (z. B. löst nicht erreichbarer Code in Java einen Fehler aus einem Grund aus, den ich gerne wissen möchte).

C # hat nicht die gleichen Probleme, aber einige. Es scheint, dass die Kompilierung in mehreren Durchgängen erfolgt und ein fehlgeschlagener Durchgang die Ausführung der weiteren Durchgänge verhindert. Aus diesem Grund wird die Fehleranzahl, die Sie erhalten, wenn Ihr Build fehlschlägt, häufig stark unterschätzt. Bei einem Durchlauf könnte es heißen, dass Sie zwei Fehler haben, aber wenn Sie diese behoben haben, erhalten Sie möglicherweise 26 neue.

Das Graben in C und C ++ zeigt einfach eine schlechte Kombination der Schwachstellen bei der Kompilierungsdiagnose von Java und C # (obwohl es genauer sein könnte zu sagen, dass Java und C # jeweils nur die Hälfte der Probleme gelöst haben). Einige Warnungen sollten eigentlich Fehler sein (zum Beispiel, wenn nicht alle Codepfade einen Wert zurückgeben), und dennoch sind sie Warnungen, da die Compilertechnologie zum Zeitpunkt der Erstellung des Standards vermutlich nicht gut genug war, um diese Art von zu erstellen Schecks obligatorisch. In der gleichen Weise prüfen Compiler häufig, ob mehr als der Standard angibt, verwenden jedoch für die zusätzlichen Ergebnisse die Warnfehlerstufe "Standard". Und oft melden Compiler nicht alle Fehler, die sie finden könnten, sofort. Es kann einige Kompilierungen erfordern, um alle zu entfernen. Ganz zu schweigen von den kryptischen Fehlern, die C ++ - Compiler gerne ausspucken.

Wenn wir nun hinzufügen, dass viele Build-Systeme konfigurierbar sind, um Fehler zu melden, wenn die Compiler Warnungen ausgeben, erhalten wir nur eine seltsame Mischung: Nicht alle Fehler sind schwerwiegend, aber einige Warnungen sollten; Nicht alle Warnungen sind verdient, aber einige werden ausdrücklich unterdrückt, ohne dass ihre Existenz weiter erwähnt wird. und manchmal werden alle Warnungen zu Fehlern.

Nicht kompilierte Sprachen haben immer noch ihren Anteil an beschissenen Fehlerberichten. Tippfehler in Python werden erst gemeldet, wenn der Code tatsächlich ausgeführt wird, und Sie können nie mehr als einen Fehler gleichzeitig auslösen, da das Skript nicht mehr ausgeführt wird, nachdem es einen Fehler erfüllt.

PHP hat auf seiner Seite eine Reihe von mehr oder weniger signifikanten Fehlerstufen und Ausnahmen. Analysefehler werden einzeln gemeldet, Warnungen sind oft so schlecht, dass sie Ihr Skript abbrechen sollten (aber nicht standardmäßig), Benachrichtigungen zeigen sehr oft schwerwiegende logische Probleme, einige Fehler sind wirklich nicht schlimm genug, um Ihr Skript zu stoppen, aber dennoch tun, und wie bei PHP üblich, gibt es dort unten einige wirklich seltsame Dinge (warum zum Teufel brauchen wir eine Fehlerstufe für schwerwiegende Fehler, die nicht wirklich schwerwiegend sind? E_RECOVERABLE_E_ERRORIch spreche mit Ihnen).

Es scheint mir, dass jede einzelne Implementierung der Compiler-Fehlerberichterstattung, die mir einfällt, fehlerhaft ist. Das ist eine echte Schande, denn alle guten Programmierer bestehen darauf, wie wichtig es ist, mit Fehlern richtig umzugehen, und können dennoch keine eigenen Tools dafür bekommen.

Was sollte Ihrer Meinung nach der richtige Weg sein, um Compilerfehler zu melden?

zneak
quelle
-1: "Nicht kompilierte Sprachen haben immer noch ihren Anteil an beschissenen Fehlerberichten" Subjektiv und argumentativ. Wirklich nicht hilfreich. Ist das eine Frage oder eine Beschwerde?
S.Lott
2
@ S.Lott Ich denke du bist hier ein bisschen am Rande. Ich finde, ich war viel schwieriger mit kompilierten Sprachen, und es schien dich nicht zu stören.
Zneak
@zneak: Die anderen Aussagen sind eher sachlich und schwerer zu analysieren. Diese Aussage erwies sich am leichtesten als subjektiv und argumentativ.
S.Lott
1
@ S.Lott Bin ich falsch zu behaupten, dass Python jeweils einen Fehler anzeigt?
Zneak
1
@ S.Lott Dann müssen sich die Dinge geändert haben, denn beim letzten Versuch hat jeder Syntaxfehler dazu geführt, dass Python nicht mehr versucht, "zu kompilieren", und ein Namensfehler hat eine Ausnahme ausgelöst und den Rest der Funktion nicht überprüft (obwohl dies nicht möglich war) Raum für die Meldung eines Fehlers pro testbarer Einheit). Meine subjektive und argumentative Aussage war eine Einführung in das, was ich für eine Tatsache hielt, aber wenn es nicht mehr stimmt, werde ich meine Frage bearbeiten. Wie funktioniert es jetzt?
Zneak

Antworten:

6

Ihre Frage scheint nicht wirklich zu sein, wie wir Compilerfehler melden, sondern es geht um die Klassifizierung von Problemen und was zu tun ist.

Wenn wir zunächst davon ausgehen, dass die Dichotomie zwischen Warnung und Fehler korrekt ist, wollen wir sehen, wie gut wir darauf aufbauen können. Einige Ideen:

  1. Unterschiedliche "Warnstufen". Viele Compiler implementieren dies sozusagen (zum Beispiel hat GCC viele Schalter, um genau zu konfigurieren, worüber gewarnt wird), aber es muss noch gearbeitet werden - zum Beispiel, um den Schweregrad einer gemeldeten Warnung zu melden und um "Warnungen" festzulegen sind Fehler "nur für Warnungen über einem bestimmten Schweregrad.

  2. Vernünftige Klassifizierung von Fehlern und Warnungen. Ein Fehler sollte nur gemeldet werden, wenn der Code nicht der Spezifikation entspricht und daher nicht kompiliert werden kann. Nicht erreichbare Anweisungen, obwohl wahrscheinlich ein Codierungsfehler, sollten eine Warnung sein , kein Fehler - der Code ist immer noch "gültig", und es gibt legitime Fälle, in denen man mit nicht erreichbarem Code kompilieren möchte (z. B. schnelle Änderungen zum Debuggen). .

Nun Dinge, mit denen ich nicht einverstanden bin:

  1. Machen Sie zusätzliche Anstrengungen, um jedes Problem zu melden. Wenn ein Fehler auftritt, wird der Build unterbrochen. Der Build ist kaputt. Der Build funktioniert erst, wenn dieser Fehler behoben ist. Daher ist es besser, diesen Fehler sofort zu melden, als "weiterzumachen", um zu versuchen, alles andere "falsch" mit dem Code zu identifizieren. Vor allem, wenn viele dieser Dinge wahrscheinlich sowieso durch den anfänglichen Fehler verursacht werden.

  2. Ihr spezifisches Beispiel für eine Warnung, die ein Fehler sein sollte. Ja, es ist wahrscheinlich ein Programmiererfehler. Nein, es sollte den Build nicht brechen. Wenn ich weiß, dass die Eingabe für die Funktion so ist, dass sie immer einen Wert zurückgibt, sollte ich in der Lage sein, den Build auszuführen und einige Tests durchzuführen, ohne diese zusätzlichen Überprüfungen hinzufügen zu müssen. Ja, es sollte eine Warnung sein. Und noch dazu eine verdammt schwere. Aber es sollte den Build an und für sich nicht brechen, es sei denn, das Kompilieren mit Warnungen ist Fehler.

Gedanken?

Anon.
quelle
Ich stimme Ihnen zu, mit Ausnahme der Punkte, in denen wir nicht einverstanden sind (duh), also ist das +1 von mir. Ich denke, es ist einfach genug, jeden Codepfad dazu zu bringen, entweder einen Wert zurückzugeben oder Ihr Programm abzubrechen, wenn man bedenkt, wie schlimm es ist, wenn Sie im Fall des undefinierten Verhaltens tatsächlich fallen.
Zneak
7

Ein Problem, das Sie angesprochen haben, war die unvollständige Meldung von Fehlern - z. B. die Meldung von zwei Fehlern. Wenn Sie diese beheben, erhalten Sie eine Menge mehr.

Dies ist (größtenteils) ein Kompromiss seitens des Compiler-Writers. Abhängig davon, welchen Fehler Sie gemacht haben, kann der Compiler sehr leicht falsch verstehen, was Sie haben, so dass er Fehler meldet, die sehr wenig mit der Realität zu tun haben. Stellen Sie sich zum Beispiel einen einfachen Tippfehler vor, bei dem Sie so etwas wie itn x;anstelle von haben int x;. Sofern Sie nichts anderes getan haben, das itnetwas bedeutet, wird dies als Fehler gemeldet. Das ist in Ordnung so weit wie es geht, aber jetzt überlegen , was als nächstes passiert - die Compiler schaut viel Code, der versucht, die Verwendung x als Variable. Sollte es A) aufhören und Sie das beheben lassen, oder B) 2000 Fehler error: "x": undeclared identifieroder etwas in dieser Reihenfolge ausspucken ? Betrachten Sie eine andere Möglichkeit:

int main()[

Dies ist ein weiterer ziemlich offensichtlicher Tippfehler - offensichtlich sollte es ein {statt eines sein [. Der Compiler kann Ihnen diesen Teil ziemlich leicht sagen - aber sollte er dann einen Fehler melden, wenn Sie beispielsweise etwas x=1;sagen error: statement only allowed inside a function?

Beachten Sie, dass dies sogar ziemlich triviale Probleme sind - viel schlimmere sind leicht zu finden (insbesondere, wie die meisten von uns wissen, wenn Sie in C ++ - Vorlagen einsteigen). Das Fazit ist, dass der Compiler-Writer normalerweise nicht in der Lage ist, Kompromisse zwischen der Meldung falscher Fehler (dh der Meldung eines Fehlers, obwohl dies in Ordnung ist) und der Nichtmeldung realer Fehler einzugehen. Es gibt einige Faustregeln, die die meisten befolgen, um zu verhindern, dass sie in beide Richtungen zu weit falsch laufen, aber fast keine davon ist annähernd perfekt.

Ein weiteres Problem, das Sie erwähnt haben, war Java und @SupressWarning. Dies ist ganz anders als oben - es wäre ziemlich trivial zu beheben. Der einzige Grund, warum es nicht behoben ist, ist, dass dies nicht zum grundlegenden "Charakter" von Java passt - dh ihrer Meinung nach "das ist kein Fehler, es ist eine Funktion". Obwohl es normalerweise ein Witz ist, sind die beteiligten Personen in diesem Fall so irregeführt, dass sie wirklich glauben, dass es wahr ist.

Das Problem, das Sie in C und C ++ mit Codepfaden erwähnen, die keinen Wert zurückgeben, besteht nicht darin, primitive Compiler zuzulassen. Es soll Jahrzehnte vorhandenen Codes zulassen , von denen einige niemand reparieren, berühren oder sogar lesen möchte. Es ist uralt und hässlich, aber es funktioniert, und niemand möchte etwas anderes, als dass es weiter funktioniert. Ob gut oder schlecht, die Sprachkomitees sind ziemlich fest entschlossen, diese Abwärtskompatibilität aufrechtzuerhalten, und erlauben daher weiterhin Dinge, die niemand wirklich mag - aber einige Leute (zumindest denken sie, dass sie es brauchen).

Jerry Sarg
quelle
3
Zusätzlich zu Ihrem Hinweis auf frühe Fehler, die viele andere verursachen, gibt es auch die Tatsache, dass spätere Durchgänge häufig so erstellt werden, dass frühere Durchgänge erfolgreich abgeschlossen werden müssen. Beispielsweise überprüft einer der frühen Durchgänge im C # -Compiler, ob das Vererbungsdiagramm keine Zyklen enthält. Sie haben kein A-Erbe von B, das von A erbt. Wenn Sie fortfahren und eine Liste erstellen möchten Von allen Fehlern danach müsste jeder spätere Durchgang in der Lage sein, Zyklen zu bewältigen - was ihn selbst bei "guten" Kompilierungen erheblich langsamer macht.
Anon.
@ Anon. Der Java-Compiler bemüht sich viel besser, frühe Durchgänge zu überstehen, und ich finde es nicht wesentlich langsamer. Für mich ist es etwas ärgerlich, wie schnell cscaufgibt.
Zneak
@zneak: Wie Jerry sagt, ist es ein Kompromiss seitens der Entwickler der Compiler. Das Schreiben einer guten Fehlerdiagnose ist tatsächlich ein sehr schwieriges Problem (siehe clang für ein Beispiel, wie weit Sie es wirklich bringen können). Hier finden Sie eine gute Beschreibung der Phasen und Durchläufe des C # -Compilers.
Dean Harding