Jonathan hat geschrieben: ↑22.03.2022, 09:16Krishty hat geschrieben: ↑21.03.2022, 23:18In der Case Study „Da taucht ein Fehler auf, aber ich weiß nicht, wo“ hätten Ausnahmen auch nicht geholfen.
Hm, in Visual Studio aktiviere ich "Break on Exception" und das funktioniert dann in der Regel auch.
Das Problem daran ist, dass es bei
jeder Ausnahme anhält … Beispiel: Für Windows 10 wurde viel interner Code auf C++-Ausnahmebehandlungen umgestellt (für UWP?). Startest du den Standard-Dialog
Datei >
Öffnen …, fliegen 30 C++-Exceptions irgendwo in
User32.dll und die IDE hält jedes. Mal. an.
Ausnahmen fliegen halt auch gern im normalen Programmverlauf … und mit der IDE-Einstellung setzt man meiner Erfahrung nach einen Haltepunkt bei
jedem Fehler.
(Ich weiß nicht, ob Visual Studios
Just my Code dem entgegenwirkt, aber das wiederum stört mich ständig, wenn ich mit externen Bibliotheken arbeite.)
Jonathan hat geschrieben: ↑22.03.2022, 09:16Naja. Was mich an Software allgemein aufregt, ist, dass es so wenig Leute schaffen, vernünftig Fehlermeldungen durchzureichen. Da arbeitet man irgendwas und ein Pop-Up taucht auf und dann steht da "Der Vorgang konnte nicht abgeschlossen werden" oder ähnliches. Klar, der normale Anwender will sich mit Details vielleicht nicht beschäftigen, aber ich denke mir dann immer, dass es irgendwo in den Tiefen des Programms einen ziemlich konkreten Hinweise darauf gibt, was gerade schief läuft, und ich den nutzen könnte um irgendwie strukturiert eine Lösung für das Problem zu finden, aber nö, all die nützliche Information wurde in irgendeiner Zwischenebene weggeworfen und ich kriege wenig mehr als "geht nicht" raus.
Absolut richtig
Jonathan hat geschrieben: ↑22.03.2022, 09:16Und in diesem Sinne mag ich die Idee, z.B. in C++ Exceptions über mehrere Ebenen zu fangen, anzureichern und wieder zu schmeißen sehr.
Ist eine Möglichkeit. Nicht meine Lieblingsmöglichkeit (dazu unten mehr), aber an sowas habe ich prinzipiell nichts auszusetzen.
Jonathan hat geschrieben: ↑22.03.2022, 09:16Ausnahmen sind ja Ausnahmen, diese Stringoperationen sollten ja, wenn alles gut geht, nie ausgeführt werden, da ist mir dann also egal wie langsam sie sind - im Extremfall würde ich auch gerne eine Sekunde länger warten, wenn dafür meine Fehlermeldung dann vernünftig ist.
Das ging jetzt gar nicht um die Geschwindigkeit. (Übrigens Hinweis zum Artikel oben: Die Quintessenz ist, C++-Ausnahmen machen
alles langsamer, nicht bloß den Fehlerfall!)
Warum ich gegen Strings in Exceptions wettere, sind 1) Lokalisierung und 2) die Trennung von Aufmachung und Inhalt. Wenn der Entwickler Inder war und seine Exception-Klasse Strings zusammenklatscht, bekommst du eben
nicht die Meldung
Error: Cannot open texture “foo”; file handles exhausted, sondern
त्रुटि: 'foo' नहीं मिल रहा है: फ़ाइल समाप्त हो गई है. Uns englischsprachigen Nordhalbkugel-Übermenschen passiert das eher selten, aber wenn deine Programme z. B. nach Südamerika gehen, sind US-Fehlermeldungen für die ähnlich brauchbar wie gar keine Fehlermeldungen. Jetzt kannst du deine Übersetzung in die Exception-Klasse reinziehen, aber das macht außer diesem einen konkreten Problem alles nur schlimmer.
Das Markup-Problem: Ich schreibe Schnittstellen und breche Abhängigkeiten auf, damit man meine Algorithmen in unterschiedlichem Kontext wiederverwenden kann – in einem Konsolenprogramm, in einem GUI-Programm, in einem Spiel, in einem Dedicated Server. Die haben komplett unterschiedliche Anforderungen daran, wie der Fehlertext auszusehen hat – das Konsolenprogramm braucht vielleicht den Code
\033[31m um den Fehler
rot zu markieren. Das GUI-Programm braucht ein
Klicken Sie OK um das Programm zu beenden hinten dran. Du kannst aber in deiner Exception-Klasse nicht wissen, in welchem Kontext du läufst – und wenn du’s tust, hast du nun eine Abhängigkeit mehr in deinem Code.
Wenn du also Ausnahmen hochreichst und anreicherst, muss das komplett auf logischer Ebene passieren, und der String darf frühestens im
catch zusammengebaut werden, weil nur der Aufrufer entscheiden kann, was angezeigt werden soll. Also darf String-Formatierung um Himmels Willen nicht in die Ausnahmeklasse; erst recht nicht in den Konstruktor. Markup und Content trennen.
Jetzt nochmal zum oben versprochenen Grund, warum C++-Ausnahmen nicht meine Lieblingsmöglichkeit sind (und Return Values auch nicht): Manchmal möchte man Fehler ignorieren.
throw fragt aber den Aufrufer mit dem
catch nicht lieb, ob der überhaupt eine Ausnahme wünscht – es schmeißt einfach und zerstört alles zwischen
throw und
catch.
Ich habe hier viel mit Parsern und Compilern zu tun, und da willst du üblicherweise einen unbarmherzigen (strikten) Modus für deine eigenen Programme haben. Bei Fremdprogrammen oder gewissen Randfällen, die Hacks erfordern, möchtest du ausgewählte nicht-kritische Standard-Verletzungen aber lieber nur als Warnung behandeln, denn als Fehler. (So eine Einstellung hat ja jeder Compiler.) Bei Spielen ähnlich: Die Engine soll im Level-Editor jeden kleinen Fehler melden, aber das tatsächliche Spiel soll um Gottes Willen nicht den Start verweigern, weil irgendwo eine Sound-Datei mono statt stereo ist!
Ich reiche bei sowas grundsätzlich nur ein Interface rein, mit einer Methode pro Fehlertyp, à
bool onInvalidVariableName(char const * name, enum NameViolation reason);
(aber halt via C realisiert weil Pörfohrmänz). Der Rückgabewert entscheidet ausschließlich:
Weiter oder nicht. Kritische Fehler bieten keinen Rückgabewert, weil sie immer abbrechen.
Ganz selten wird auch mal ein
enum aus mehreren Möglichkeiten zurückgegeben, wie
mach weiter aber ignoriere den Wert vs.
mach weiter aber überschreibe den bestehenden Wert mit dem fehlerhaften.
Ob der Aufrufer die Fehlermeldung nun via Konsolenausgabe mit Farb-Code realisieren möchte, oder als Message Box, oder an eine in Echtzeit laufende Spiele-Engine weitergibt, oder eine Ausnahme wirft, oder das erst lokalisieren möchte, oder auf
ERROR_PERMISSION_DENIED beim Öffnen einer Datei anders reagieren muss als auf
ERROR_PATH_NOT_FOUND, oder den User erstmal in der Konsole
y oder
n eintippen lässt, ist mir dann egal. Kommt
true zurück, mache ich weiter. Kommt
false zurück, nicht. Und dann gebe ich auch keinen Fehler-Code hoch, weil der Aufrufer seine Fehlerbeschreibung schon bekommen hat, und wenn er die später noch braucht, muss er die selber speichern. Ist alles nicht mehr mein Problem. (Im Beispiel oben ist nicht einmal eine Zeilennummer drin, denn falls der Aufrufer die braucht, kann er sie sich aus dem Zeiger auf den Bezeichner selber berechnen. Schließlich hat er mir die Datei reingereicht – dann weiß er auch, an welcher Adresse sie beginnt, und kann die Newlines dazwischen zählen. Nicht mein Problem.)
Der Nachteil ist Tipperei, weil man nun für jeden Aufruf einmal diese Schnittstelle implementiert haben muss. Die gute Nachricht ist, dass auch diese sich wiederverwenden lässt.