Kurze Antwort: Lesen Sie Sensible Error Handling 1 , Sensible Error Handling 2 und Sensible Error Handling 3 von Niklas Frykholm. Lesen Sie alle Artikel in diesem Blog, während Sie gerade dabei sind. Ich werde nicht sagen, dass ich mit allem einverstanden bin, aber das meiste davon ist Gold.
Verwenden Sie keine Ausnahmen. Es gibt eine Vielzahl von Gründen. Ich werde die wichtigsten auflisten.
Sie können in der Tat langsamer sein, obwohl dies auf neueren Compilern ziemlich minimiert ist. Einige Compiler unterstützen "Zero-Overhead-Ausnahmen" für Codepfade, die eigentlich keine Ausnahmen auslösen (obwohl dies eine kleine Lüge ist, da die Ausnahmebehandlung immer noch zusätzliche Daten erfordert, die die Größe Ihrer ausführbaren Datei / DLL aufblähen). Das Endergebnis ist jedoch, dass die Verwendung von Ausnahmen langsamer ist und dass Sie diese in allen leistungskritischen Codepfaden unbedingt vermeiden sollten. Abhängig von Ihrem Compiler kann die Aktivierung dieser Funktionen zusätzlichen Aufwand verursachen. Die Codegröße wird immer aufgebläht, was in vielen Fällen erheblich ist und die Leistung der heutigen Hardware erheblich beeinträchtigen kann.
Ausnahmen machen den Code viel zerbrechlicher. Es gibt eine berüchtigte Grafik (die ich derzeit leider nicht finden kann), die im Grunde genommen nur die Schwierigkeit zeigt, ausnahmesicheren Code im Vergleich zu ausnahmesicherem Code zu schreiben, und die erstere ist ein deutlich größerer Balken. Es gibt einfach viele kleine Fallstricke mit Ausnahmen und viele Möglichkeiten, Code dafür zu schreiben der aussieht Ausnahme sicher , aber wirklich nicht. Sogar das gesamte C ++ 11-Komitee hat diesen Fehler begangen und vergessen, wichtige Hilfsfunktionen für die korrekte Verwendung von std :: unique_ptr hinzuzufügen, und selbst bei diesen Hilfsfunktionen ist mehr Tipparbeit erforderlich, um sie zu verwenden, als nicht, und die meisten Programmierer haben gewonnen. ' Ich weiß nicht einmal, was los ist, wenn sie es nicht tun.
Insbesondere für die Spieleindustrie unterstützen die Compiler / Laufzeiten einiger Konsolen, die vom Hersteller bereitgestellt werden, Ausnahmen nicht oder überhaupt nicht. Wenn in Ihrem Code Ausnahmen verwendet werden, müssen Sie möglicherweise noch in der heutigen Zeit Teile Ihres Codes neu schreiben, um ihn auf neue Plattformen zu portieren. (Unsicher, ob sich dies in den 7 Jahren seit der Veröffentlichung der genannten Konsolen geändert hat. Wir verwenden nur keine Ausnahmen, haben sie sogar in den Compilereinstellungen deaktiviert, sodass ich nicht weiß, dass jemand, mit dem ich spreche, sie kürzlich überprüft hat.)
Die allgemeine Denkweise ist ziemlich klar: Verwenden Sie Ausnahmen für außergewöhnliche Umstände. Verwenden Sie sie, wenn in Ihrem Programm die Meldung "Ich habe keine Ahnung, was zu tun ist, hoffentlich jemand anderes. Ich werde also eine Ausnahme auslösen und sehen, was passiert." Verwenden Sie sie, wenn keine andere Option sinnvoll ist. Verwenden Sie sie, wenn es Ihnen egal ist, ob Sie versehentlich ein wenig Speicher verlieren oder eine Ressource nicht bereinigen, weil Sie die Verwendung geeigneter intelligenter Handles durcheinander gebracht haben. Verwenden Sie sie in allen anderen Fällen nicht.
In Bezug auf Code wie Ihr Beispiel haben Sie mehrere andere Möglichkeiten, um das Problem zu beheben. Eine der robusteren Methoden, die in Ihrem einfachen Beispiel nicht unbedingt die beste ist, ist die Tendenz zu monadischen Fehlertypen. Das heißt, createText () kann einen benutzerdefinierten Handle-Typ anstelle einer Ganzzahl zurückgeben. Dieser Handle-Typ verfügt über Accessoren zum Aktualisieren oder Steuern des Texts. Wenn das Handle in einen Fehlerzustand versetzt wird (weil createText () fehlgeschlagen ist), schlagen weitere Aufrufe des Handles einfach unbemerkt fehl. Sie können auch das Handle abfragen, um festzustellen, ob ein Fehler aufgetreten ist und wenn ja, wie der letzte Fehler lautete. Dieser Ansatz hat mehr Overhead als andere Optionen, ist aber ziemlich solide. Verwenden Sie es in Fällen, in denen Sie eine lange Reihe von Operationen in einem Kontext ausführen müssen, in dem eine einzelne Operation in der Produktion fehlschlagen könnte, Sie aber nicht / nicht / gewinnen können. '
Eine Alternative zur Implementierung der monadischen Fehlerbehandlung besteht darin, die Methoden des Kontextobjekts nicht mit benutzerdefinierten Handle-Objekten, sondern mit ungültigen Handle-IDs umzugehen. Wenn createText () beispielsweise -1 zurückgibt, wenn dies fehlschlägt, sollten alle anderen Aufrufe von m_statistics, die eines dieser Handles verwenden, ordnungsgemäß beendet werden, wenn -1 übergeben wird.
Sie können den Fehlerdruck auch in die Funktion einfügen, die tatsächlich fehlschlägt. In Ihrem Beispiel hat createText () wahrscheinlich viel mehr Informationen darüber, was schief gelaufen ist, sodass ein aussagekräftigerer Fehler in das Protokoll geschrieben werden kann. In diesem Fall ist es wenig vorteilhaft, die Fehlerbehandlung / den Ausdruck an die Anrufer weiterzuleiten. Tun Sie dies, wenn die Anrufer die Behandlung anpassen müssen (oder die Abhängigkeitsinjektion verwenden müssen). Beachten Sie, dass eine In-Game-Konsole, die angezeigt wird, wenn ein Fehler protokolliert wird, eine gute Idee ist und auch hier Abhilfe schafft.
Die beste Option (bereits in der verknüpften Artikelserie oben dargestellt) für Anrufe, von denen Sie nicht erwarten, dass sie in einer vernünftigen Umgebung fehlschlagen - wie das einfache Erstellen von Text-Blobs für ein Statistiksystem - besteht darin, nur die Funktion zu haben Das ist fehlgeschlagen (createText in Ihrem Beispiel). Sie können sich ziemlich sicher sein, dass createText () in der Produktion nicht fehlschlägt, es sei denn, etwas wurde vollständig gelöscht (z. B. der Benutzer hat Schriftartendatendateien gelöscht oder hat aus irgendeinem Grund nur 256 MB Speicher usw.). In vielen dieser Fälle kann man nicht einmal etwas Gutes tun, wenn ein Fehler auftritt. Zu wenig Speicher? Möglicherweise können Sie nicht einmal eine Zuordnung vornehmen, um ein ansprechendes GUI-Panel zu erstellen, das dem Benutzer den OOM-Fehler anzeigt. Fehlende Schriften? Erschwert die Anzeige von Fehlern für den Benutzer. Was auch immer falsch ist,
Nur Abstürze sind völlig in Ordnung, solange Sie (a) den Fehler in einer Protokolldatei protokollieren und (b) nur Fehler beheben, die nicht durch regelmäßige Benutzeraktionen verursacht werden.
Für viele Server-Apps, bei denen die Verfügbarkeit von entscheidender Bedeutung ist und die Überwachung des Watchdogs nicht ausreicht, würde ich nicht dasselbe sagen, aber das ist etwas ganz anderes als bei der Entwicklung von Spieleclients . Ich möchte Sie auch nachdrücklich davon abhalten, C / C ++ zu verwenden, da die Ausnahmebehandlungsfunktionen anderer Sprachen in der Regel nicht wie C ++ funktionieren, da es sich um verwaltete Umgebungen handelt, die nicht alle Ausnahmesicherheitsprobleme von C ++ aufweisen. Performance-Bedenken werden gemindert, da sich Server in der Regel mehr auf Parallelität und Durchsatz konzentrieren als auf Garantien mit minimaler Latenz wie bei den Spieleclients. Sogar Action-Game-Server für Schützen und Ähnliches können recht gut funktionieren, wenn sie beispielsweise in C # geschrieben sind, da sie die Hardware selten an ihre Grenzen bringen, wie es FPS-Clients tun.