Ich versuche, meinen Teamleiter davon zu überzeugen, die Verwendung von Ausnahmen in C ++ zuzulassen, anstatt einen Bool isSuccessful
oder eine Aufzählung mit dem Fehlercode zurückzugeben. Dieser Kritik an ihm kann ich jedoch nicht entgegentreten.
Betrachten Sie diese Bibliothek:
class OpenFileException() : public std::runtime_error {
}
void B();
void C();
/** Does blah and blah. */
void B() {
// The developer of B() either forgot to handle C()'s exception or
// chooses not to handle it and let it go up the stack.
C();
};
/** Does blah blah.
*
* @raise OpenFileException When we failed to open the file. */
void C() {
throw new OpenFileException();
};
Betrachten Sie einen Entwickler, der die
B()
Funktion aufruft . Er prüft die Dokumentation und stellt fest, dass es keine Ausnahmen gibt, also versucht er nicht, etwas zu fangen. Dieser Code könnte das Programm in der Produktion zum Absturz bringen.Betrachten Sie einen Entwickler, der die
C()
Funktion aufruft . Er prüft die Dokumentation nicht und erfasst keine Ausnahmen. Der Aufruf ist unsicher und kann das Programm in der Produktion zum Absturz bringen.
Aber wenn wir auf diese Weise nach Fehlern suchen:
void old_C(myenum &return_code);
Ein Entwickler, der diese Funktion verwendet, wird vom Compiler gewarnt, wenn er dieses Argument nicht angibt, und er würde sagen "Aha, dies gibt einen Fehlercode zurück, auf den ich prüfen muss."
Wie kann ich Ausnahmen sicher verwenden, damit es eine Art Vertrag gibt?
quelle
Either
/Result
monad den Fehler in einer typsicheren kompositorischen Weise zurückzugebenAntworten:
Dies ist eine berechtigte Kritik an Ausnahmen. Sie sind oft weniger sichtbar als eine einfache Fehlerbehandlung wie die Rückgabe eines Codes. Und es gibt keine einfache Möglichkeit, einen "Vertrag" durchzusetzen. Ein Teil des Punktes besteht darin, Ihnen zu ermöglichen, dass Ausnahmen auf einer höheren Ebene abgefangen werden (wenn Sie jede Ausnahme auf jeder Ebene abfangen müssen, wie unterscheidet es sich überhaupt von der Rückgabe eines Fehlercodes?). Dies bedeutet, dass Ihr Code von einem anderen Code aufgerufen werden kann, der ihn nicht ordnungsgemäß verarbeitet.
Ausnahmen haben Nachteile; Sie müssen einen Fall basierend auf Kosten-Nutzen machen.
Ich fand diese beiden Artikel hilfreich: Die Notwendigkeit von Ausnahmen und Alles falsch mit Ausnahmen. Außerdem bietet dieser Blog-Beitrag Meinungen vieler Experten zu Ausnahmen, mit einem Schwerpunkt auf C ++ . Während die Meinung von Experten eher für Ausnahmen zu sprechen scheint, ist dies alles andere als ein klarer Konsens.
Es könnte sein , dass dies nicht der richtige Kampf ist, um Ihren Teamleiter zu überzeugen . Besonders nicht mit altem Code. Wie im zweiten Link oben erwähnt:
Das Hinzufügen von ein wenig Code, der Ausnahmen für ein Projekt verwendet, das dies hauptsächlich nicht tut, ist wahrscheinlich keine Verbesserung. Das Nichtverwenden von Ausnahmen in ansonsten gut geschriebenem Code ist weit von einem katastrophalen Problem entfernt. Je nach Anwendung und dem Experten, den Sie fragen, ist dies möglicherweise überhaupt kein Problem. Sie müssen Ihre Schlachten auswählen.
Dies ist wahrscheinlich kein Argument, mit dem ich mich befassen würde - zumindest nicht, bis ein neues Projekt gestartet wird. Und selbst wenn Sie ein neues Projekt haben, wird es von einem älteren Code verwendet oder verwendet?
quelle
Es gibt buchstäblich ganze Bücher zu diesem Thema, daher ist jede Antwort bestenfalls eine Zusammenfassung. Hier sind einige der wichtigen Punkte, die ich aufgrund Ihrer Frage für angebracht halte. Es ist keine vollständige Liste.
Ausnahmen sollen NICHT überall aufgefangen werden.
Solange sich in der Hauptschleife ein allgemeiner Ausnahmebehandler befindet (abhängig von der Art der Anwendung (Webserver, lokaler Dienst, Befehlszeilendienstprogramm ...)), verfügen Sie normalerweise über alle erforderlichen Ausnahmebehandler.
In meinem Code gibt es - wenn überhaupt - nur wenige catch-Anweisungen außerhalb der Hauptschleife. Und das scheint der gängige Ansatz in modernem C ++ zu sein.
Ausnahmen und Rückkehrcodes schließen sich nicht gegenseitig aus.
Sie sollten dies nicht zu einem Alles-oder-Nichts-Ansatz machen. Ausnahmen sollten für Ausnahmesituationen verwendet werden. Dinge wie "Konfigurationsdatei nicht gefunden", "Datenträger voll" oder alles andere, was lokal nicht gehandhabt werden kann.
Häufige Fehler wie die Überprüfung, ob ein vom Benutzer angegebener Dateiname gültig ist, sind kein Anwendungsfall für Ausnahmen. Verwenden Sie in diesen Fällen stattdessen einen Rückgabewert.
Wie Sie aus den obigen Beispielen ersehen können, kann "Datei nicht gefunden" je nach Anwendungsfall entweder eine Ausnahme oder ein Rückkehrcode sein: "ist Teil der Installation" versus "Benutzer kann Tippfehler machen."
Es gibt also keine absolute Regel. Eine grobe Richtlinie ist: Wenn es lokal gehandhabt werden kann, machen Sie es zu einem Rückgabewert; Wenn Sie nicht lokal damit umgehen können, lösen Sie eine Ausnahme aus.
Die statische Überprüfung von Ausnahmen ist nicht sinnvoll.
Da Ausnahmen ohnehin nicht lokal behandelt werden sollen, ist es normalerweise nicht wichtig, welche Ausnahmen geworfen werden können. Die einzige nützliche Information ist, ob eine Ausnahme ausgelöst werden kann.
Java hat eine statische Prüfung, wird jedoch normalerweise als fehlgeschlagen angesehen, und die meisten Sprachen, insbesondere C #, haben diese Art der statischen Prüfung nicht. Dies ist eine gute Lektüre über die Gründe, warum C # es nicht hat.
Aus diesen Gründen hat C ++ es
throw(exceptionA, exceptionB)
zugunsten von abgelehntnoexcept(true)
. Die Standardeinstellung ist, dass eine Funktion ausgelöst werden kann, daher sollten Programmierer dies erwarten, sofern die Dokumentation nicht ausdrücklich etwas anderes verspricht.Das Schreiben von Exception-Safe-Code hat nichts mit dem Schreiben von Exception-Handlern zu tun.
Ich würde eher sagen, dass es beim Schreiben von ausnahmesicherem Code darum geht, das Schreiben von Ausnahmehandlern zu vermeiden!
Die meisten Best Practices zielen darauf ab, die Anzahl der Ausnahmebehandler zu reduzieren. Das einmalige Schreiben und automatische Aufrufen von Code - z. B. über RAII - führt zu weniger Fehlern als das Kopieren und Einfügen desselben Codes überall.
quelle
IOException
mal , dasselbe machen Sie auch mit 99% von s . Die überprüfte Unterteilung ist willkürlich und unbrauchbar.C ++ - Programmierer suchen nicht nach Ausnahmespezifikationen. Sie suchen Ausnahmegarantien.
Angenommen, ein Teil des Codes hat eine Ausnahme ausgelöst. Welche Annahmen kann der Programmierer treffen, die noch gültig sind? Was garantiert der Code in Bezug auf die Art und Weise, in der der Code geschrieben wurde, nach einer Ausnahme?
Oder ist es möglich, dass ein bestimmtes Stück Code garantieren kann, dass es niemals geworfen wird (dh nichts anderes als das Beenden des Betriebssystemprozesses)?
Das Wort "Rollback" kommt häufig in Diskussionen über Ausnahmen vor. Ein Beispiel für eine Ausnahmegarantie ist die Möglichkeit, ein Rollback in einen gültigen Status durchzuführen (der explizit dokumentiert ist). Wenn keine Ausnahmegarantie besteht, sollte ein Programm sofort beendet werden, da nicht einmal garantiert wird, dass der Code, den es danach ausführt, wie beabsichtigt funktioniert. Wenn beispielsweise der Speicher beschädigt ist, ist jede weitere Operation ein technisch undefiniertes Verhalten.
Verschiedene C ++ - Programmiertechniken fördern Ausnahmegarantien. RAII (Scope-based Resource Management) bietet einen Mechanismus, mit dem Bereinigungscode ausgeführt und sichergestellt werden kann, dass Ressourcen sowohl in normalen als auch in Ausnahmefällen freigegeben werden. Wenn Sie eine Kopie der Daten erstellen, bevor Sie Änderungen an Objekten vornehmen, können Sie den Status des Objekts wiederherstellen, wenn der Vorgang fehlschlägt. Und so weiter.
Die Antworten auf diese StackOverflow-Frage geben einen Einblick in die langen Wege, die C ++ - Programmierer gehen, um alle möglichen Fehlermodi zu verstehen, die mit ihrem Code einhergehen könnten, und um zu versuchen, die Gültigkeit des Programmstatus trotz Fehlern zu gewährleisten. Die zeilenweise Analyse von C ++ - Code wird zur Gewohnheit.
Bei der Entwicklung in C ++ (für die Produktion) kann man es sich nicht leisten, die Details zu beschönigen. Binär-Blob (Nicht-Open-Source) ist der Fluch von C ++ - Programmierern. Wenn ich ein binäres Blob aufrufen muss und das Blob fehlschlägt, ist Reverse Engineering das, was ein C ++ - Programmierer als Nächstes tun würde.
Referenz: http://en.cppreference.com/w/cpp/language/exceptions#Exception_safety - siehe unter Exception Safety.
C ++ hatte einen fehlgeschlagenen Versuch, Ausnahmespezifikationen zu implementieren. Eine spätere Analyse in anderen Sprachen besagt, dass Ausnahmespezifikationen einfach nicht praktikabel sind.
Warum es ein fehlgeschlagener Versuch ist: Um ihn strikt durchzusetzen, muss er Teil des Typsystems sein. Ist es aber nicht. Der Compiler prüft nicht auf Ausnahmespezifikationen.
Warum C ++ dies gewählt hat und warum Erfahrungen mit anderen Sprachen (Java) beweisen, dass die Ausnahmespezifikation umstritten ist: Wenn Sie die Implementierung einer Funktion ändern (zum Beispiel müssen Sie eine andere Funktion aufrufen, die möglicherweise eine neue Art von auslöst Ausnahme) bedeutet eine strikte Durchsetzung der Ausnahmespezifikation, dass Sie diese Spezifikation ebenfalls aktualisieren müssen. Dies setzt sich fort - möglicherweise müssen Sie die Ausnahmespezifikationen für Dutzende oder Hunderte von Funktionen aktualisieren, um eine einfache Änderung zu erzielen. Bei abstrakten Basisklassen (dem C ++ - Äquivalent von Interfaces) wird es schlimmer. Wenn die Ausnahmespezifikation für Schnittstellen erzwungen wird, dürfen Implementierungen von Schnittstellen keine Funktionen aufrufen, die verschiedene Arten von Ausnahmen auslösen.
Referenz: http://www.gotw.ca/publications/mill22.htm
Ab C ++ 17 kann das
[[nodiscard]]
Attribut für Funktionsrückgabewerte verwendet werden (siehe: https://stackoverflow.com/questions/39327028/can-ac-function-be-declared-that-the-return-value) -kann-nicht-ignoriert werden ).Wenn ich also eine Codeänderung vornehme und dies eine neue Art von Fehlerbedingung einführt (dh eine neue Art von Ausnahme), handelt es sich dann um eine brechende Änderung? Sollte es den Anrufer gezwungen haben, den Code zu aktualisieren, oder zumindest über die Änderung gewarnt werden?
Wenn Sie die Argumente akzeptieren, mit denen C ++ - Programmierer nach Ausnahmegarantien anstelle von Ausnahmespezifikationen suchen, lautet die Antwort: Wenn die neue Art der Fehlerbedingung keine der Ausnahmebedingungen aufhebt, die den zuvor versprochenen Code garantieren, handelt es sich nicht um eine brechende Änderung.
quelle
std::exception
) und Ihre eigene Basisausnahme ausgelöst werden. Auf ein ganzes Projekt angewendet bedeutet dies jedoch, dass jede einzelne Funktion dieselbe Spezifikation hat: Rauschen.catch
der Typ des Ausnahmeobjekts zugrunde liegt, wenn in vielen Fällen nicht so sehr die direkte Ursache der Ausnahme von Bedeutung ist, sondern vielmehr, worum es geht der Systemzustand. Leider sagt der Typ einer Ausnahme im Allgemeinen nichts darüber aus, ob Code dazu geführt hat, dass ein Teil des Codes auf eine Weise unterbrochen wurde, die ein Objekt in einem teilweise aktualisierten (und damit ungültigen) Zustand zurückließ.Es ist absolut sicher. Er muss nicht an jedem Ort Ausnahmen fangen, er kann einfach einen Versuch / Fang an einen Ort werfen, an dem er tatsächlich etwas Nützliches dagegen unternehmen kann. Es ist nur unsicher, wenn er zulässt, dass es aus einem Thread austritt, aber es ist normalerweise nicht schwer, dies zu verhindern.
quelle
C()
und folglich nicht vor dem Code schützt, der nach dem Überspringen des Aufrufs auftritt. Wenn dieser Code erforderlich ist, um zu gewährleisten, dass eine gewisse Invarianz wahr bleibt, ist diese Garantie bei der ersten Ausnahme, die eintritt, hinfälligC()
. Es gibt keine absolut ausnahmesichere Software, und mit Sicherheit nicht dort, wo niemals Ausnahmen erwartet wurden.C()
ist ein großer Fehler. Die Standardeinstellung in C ++ ist, dass jede Funktion ausgelöst werden kann - es ist ein expliziternoexcept(true)
Befehl erforderlich , um den Compiler anders zu informieren. In Abwesenheit dieses Versprechens muss ein Programmierer annehmen, dass eine Funktion werfen kann.C()
Funktionen, die bei Vorhandensein von Ausnahmen nicht funktionieren. Dies ist wirklich eine sehr unkluge Sache, es ist zum Scheitern verurteilt, zu einer Tonne von Problemen zu führen.Wenn Sie ein kritisches System erstellen, sollten Sie die Anweisungen Ihres Teamleiters befolgen und keine Ausnahmen verwenden. Dies ist die AV-Regel 208 in den C ++ - Codierungsstandards von Lockheed Martin für Joint Strike Fighter-Luftfahrzeuge . Auf der anderen Seite haben MISRA C ++ - Richtlinien sehr spezifische Regeln, wann Ausnahmen verwendet werden können und wann nicht, wenn Sie ein MISRA-kompatibles Softwaresystem erstellen.
Wenn Sie kritische Systeme erstellen, können Sie auch statische Analysetools ausführen. Viele statische Analysewerkzeuge warnen, wenn Sie den Rückgabewert einer Methode nicht überprüfen, wodurch Fälle fehlender Fehlerbehandlung sofort sichtbar werden. Nach meinem besten Wissen ist eine ähnliche Toolunterstützung zum Erkennen einer ordnungsgemäßen Ausnahmebehandlung nicht so stark.
Letztendlich würde ich argumentieren, dass vertragliches Design und defensive Programmierung in Verbindung mit statischer Analyse für kritische Softwaresysteme sicherer sind als Ausnahmen.
quelle