Warum sollten Ausnahmen konservativ verwendet werden?


80

Ich sehe / höre oft Leute sagen, dass Ausnahmen nur selten verwendet werden sollten, aber niemals erklären, warum. Das mag zwar zutreffen, aber die Begründung ist normalerweise ein Problem: "Es wird aus einem bestimmten Grund als Ausnahme bezeichnet", was für mich die Art von Erklärung zu sein scheint, die von einem angesehenen Programmierer / Ingenieur niemals akzeptiert werden sollte.

Es gibt eine Reihe von Problemen, mit deren Hilfe eine Ausnahme gelöst werden kann. Warum ist es unklug, sie für den Kontrollfluss zu verwenden? Welche Philosophie steckt dahinter, außergewöhnlich konservativ mit ihrer Verwendung umzugehen? Semantik? Performance? Komplexität? Ästhetik? Konvention?

Ich habe bereits einige Analysen zur Leistung gesehen, aber auf einer Ebene, die für einige Systeme relevant und für andere irrelevant wäre.

Auch hier bin ich nicht unbedingt anderer Meinung, dass sie für besondere Umstände aufbewahrt werden sollten, aber ich frage mich, was die Konsensgrundlage ist (falls es so etwas gibt).



32
Kein Duplikat. Im verknüpften Beispiel geht es darum, ob die Ausnahmebehandlung überhaupt nützlich ist. Hier geht es um die Unterscheidung zwischen der Verwendung von Ausnahmen und der Verwendung eines anderen Fehlerberichterstattungsmechanismus.
Adrian McCarthy

1
und wie wäre es mit diesem einen stackoverflow.com/questions/1385172/… ?
Stijn

1
Es gibt keine Konsensgründe. Unterschiedliche Personen haben unterschiedliche Meinungen über die "Angemessenheit" des Auslösens von Ausnahmen, und diese Meinungen werden im Allgemeinen von der Sprache beeinflusst, in der sie sich entwickeln. Sie haben diese Frage mit C ++ markiert, aber ich vermute, wenn Sie sie mit Java markieren, erhalten Sie unterschiedliche Meinungen.
Charles Salvia

5
Nein, es sollte kein Wiki sein. Die Antworten hier erfordern Fachwissen und sollten mit Vertretern belohnt werden.
Bobobobo

Antworten:


91

Der primäre Reibungspunkt ist die Semantik. Viele Entwickler missbrauchen Ausnahmen und werfen sie bei jeder Gelegenheit aus. Die Idee ist, Ausnahme für etwas außergewöhnliche Situation zu verwenden. Beispielsweise zählen falsche Benutzereingaben nicht als Ausnahme, da Sie davon ausgehen, dass dies geschieht und dafür bereit ist. Wenn Sie jedoch versucht haben, eine Datei zu erstellen, und nicht genügend Speicherplatz auf der Festplatte vorhanden war, ist dies eine eindeutige Ausnahme.

Ein weiteres Problem ist, dass Ausnahmen oft geworfen und verschluckt werden. Entwickler verwenden diese Technik, um das Programm einfach "zum Schweigen zu bringen" und es so lange wie möglich laufen zu lassen, bis es vollständig zusammenbricht. Das ist sehr falsch. Wenn Sie keine Ausnahmen verarbeiten, wenn Sie nicht angemessen reagieren, indem Sie einige Ressourcen freigeben, wenn Sie das Auftreten von Ausnahmen nicht protokollieren oder den Benutzer zumindest nicht benachrichtigen, verwenden Sie keine Ausnahme für das, was sie bedeuten.

Beantworten Sie Ihre Frage direkt. Ausnahmen sollten selten verwendet werden, da Ausnahmesituationen selten und Ausnahmen teuer sind.

Selten, weil Sie nicht erwarten, dass Ihr Programm bei jedem Tastendruck oder bei jeder fehlerhaften Benutzereingabe abstürzt. Angenommen, auf die Datenbank kann plötzlich nicht mehr zugegriffen werden, es ist möglicherweise nicht genügend Speicherplatz auf der Festplatte vorhanden, ein Dienst eines Drittanbieters, auf den Sie angewiesen sind, ist offline. Dies alles kann passieren, aber ziemlich selten sind dies eindeutige Ausnahmefälle.

Teuer, da das Auslösen einer Ausnahme den normalen Programmablauf unterbricht. Die Laufzeit wickelt den Stapel ab, bis ein geeigneter Ausnahmebehandler gefunden wird, der die Ausnahme behandeln kann. Außerdem werden die Anrufinformationen auf dem gesamten Weg gesammelt, die an das Ausnahmeobjekt übergeben werden sollen, das der Handler erhält. Das alles hat Kosten.

Dies bedeutet nicht, dass es keine Ausnahme bei der Verwendung von Ausnahmen geben kann (Lächeln). Manchmal kann es die Codestruktur vereinfachen, wenn Sie eine Ausnahme auslösen, anstatt Rückkehrcodes über viele Ebenen weiterzuleiten. In der Regel ist es besser, eine andere Lösung zu finden, wenn Sie erwarten, dass eine Methode häufig aufgerufen wird und die Hälfte der Zeit eine "außergewöhnliche" Situation entdeckt. Wenn Sie jedoch die meiste Zeit einen normalen Betriebsfluss erwarten, während diese "außergewöhnliche" Situation nur in seltenen Fällen auftreten kann, ist es in Ordnung, eine Ausnahme auszulösen.

@Comments: Eine Ausnahme kann definitiv in weniger außergewöhnlichen Situationen verwendet werden, wenn dies Ihren Code einfacher und einfacher machen könnte. Diese Option ist offen, aber ich würde sagen, dass sie in der Praxis ziemlich selten vorkommt.

Warum ist es unklug, sie für den Kontrollfluss zu verwenden?

Weil Ausnahmen den normalen "Kontrollfluss" stören. Sie lösen eine Ausnahme aus und die normale Ausführung des Programms wird abgebrochen, wodurch Objekte möglicherweise in einem inkonsistenten Zustand bleiben und einige offene Ressourcen nicht freigegeben werden. Sicher, C # hat die using-Anweisung, die sicherstellt, dass das Objekt auch dann entsorgt wird, wenn eine Ausnahme vom using-Body ausgelöst wird. Aber lassen Sie uns für den Moment von der Sprache abstrahieren. Angenommen, das Framework entsorgt keine Objekte für Sie. Sie machen es manuell. Sie haben ein System zum Anfordern und Freigeben von Ressourcen und Speicher. Sie haben systemweit vereinbart, wer in welchen Situationen für die Freigabe von Objekten und Ressourcen verantwortlich ist. Sie haben Regeln für den Umgang mit externen Bibliotheken. Es funktioniert hervorragend, wenn das Programm dem normalen Betriebsablauf folgt. Aber plötzlich, mitten in der Ausführung, löst man eine Ausnahme aus. Die Hälfte der Ressourcen bleibt frei. Die Hälfte wurde noch nicht angefordert. Wenn die Operation jetzt als Transaktion gedacht war, ist sie unterbrochen. Ihre Regeln für den Umgang mit Ressourcen funktionieren nicht, da die Codeteile, die für die Freigabe von Ressourcen verantwortlich sind, einfach nicht ausgeführt werden. Wenn jemand anderes diese Ressourcen nutzen wollte, könnte er sie in einem inkonsistenten Zustand finden und ebenfalls abstürzen, weil er diese spezielle Situation nicht vorhersagen konnte.

Angenommen, Sie wollten, dass eine Methode M () die Methode N () aufruft, um einige Arbeiten auszuführen und eine Ressource zu arrangieren. Geben Sie sie dann an M () zurück, die sie verwendet und dann entsorgt. Fein. Jetzt geht in N () etwas schief und es wird eine Ausnahme ausgelöst, die Sie in M ​​() nicht erwartet haben, sodass die Ausnahme nach oben sprudelt, bis sie möglicherweise in einer Methode C () abgefangen wird, die keine Ahnung hat, was tief im Inneren passiert ist in N () und ob und wie einige Ressourcen freigegeben werden.

Mit Ausnahmen schaffen Sie eine Möglichkeit, Ihr Programm in viele neue unvorhersehbare Zwischenzustände zu bringen, die schwer vorherzusagen, zu verstehen und zu handhaben sind. Es ist etwas ähnlich wie bei GOTO. Es ist sehr schwierig, ein Programm zu entwerfen, das seine Ausführung zufällig von einem Ort zum anderen springen kann. Es wird auch schwierig sein, es zu warten und zu debuggen. Wenn das Programm immer komplexer wird, verlieren Sie nur einen Überblick darüber, was wann und wo weniger passiert, um es zu beheben.


5
Wenn Sie eine Funktion, die eine Ausnahme auslöst, mit einer Funktion vergleichen, die einen Fehlercode zurückgibt, ist das Abwickeln des Stapels der gleiche, es sei denn, mir fehlt etwas.
Catskul

9
@Catskul: nicht unbedingt. Eine Funktionsrückgabe gibt die Steuerung direkt an den unmittelbaren Aufrufer zurück. Eine ausgelöste Ausnahme gibt die Kontrolle an den ersten Catch-Handler für diesen Ausnahmetyp (oder eine Basisklasse davon oder "...") im gesamten aktuellen Aufrufstapel zurück.
Jon-Hanson

5
Es sollte auch beachtet werden, dass Debugger bei Ausnahmen häufig standardmäßig unterbrochen werden. Der Debugger wird viel kaputt gehen, wenn Sie Ausnahmen für Bedingungen verwenden, die nicht ungewöhnlich sind.
Qwertie

5
@jon: Das würde nur passieren, wenn es nicht erfasst würde und mehr Bereiche umgehen müssten, um die Fehlerbehandlung zu erreichen. In demselben Fall, in dem Rückgabewerte verwendet werden (und der auch über mehrere Bereiche weitergegeben werden muss), würde dieselbe Menge an Stapelabwicklung auftreten.
Catskul

3
-1 Entschuldigung. Es ist sinnlos darüber zu sprechen, wofür Ausnahmeeinrichtungen "gedacht" sind. Sie sind nur ein Mechanismus, der zur Behandlung von Ausnahmesituationen wie Speichermangel usw. verwendet werden kann. Dies bedeutet jedoch nicht, dass sie nicht auch für andere Zwecke verwendet werden sollten. Was ich sehen möchte, ist eine Erklärung, warum sie nicht für andere Zwecke verwendet werden sollten. Obwohl Ihre Antwort ein wenig darüber spricht, wird sie mit viel Gerede darüber gemischt, wofür Ausnahmen "gedacht" sind.
j_random_hacker

61

Während "Ausnahmen unter außergewöhnlichen Umständen auslösen" die eindeutige Antwort ist, können Sie tatsächlich definieren, was diese Umstände sind: Wenn die Voraussetzungen erfüllt sind, aber die Nachbedingungen nicht erfüllt werden können . Auf diese Weise können Sie strengere, strengere und nützlichere Nachbedingungen schreiben, ohne die Fehlerbehandlung zu beeinträchtigen. Andernfalls müssen Sie ausnahmslos die Nachbedingung ändern, um jeden möglichen Fehlerzustand zu berücksichtigen.

  • Voraussetzungen müssen erfüllt sein, bevor eine Funktion aufgerufen wird .
  • Nachbedingung ist das, was die Funktion nach ihrer Rückkehr garantiert .
  • Die Ausnahmesicherheit gibt an, wie sich Ausnahmen auf die interne Konsistenz einer Funktion oder Datenstruktur auswirken und sich häufig auf von außen übergebenes Verhalten beziehen (z. B. Funktor, ctor eines Vorlagenparameters usw.).

Konstruktoren

Zu jedem Konstruktor für jede Klasse, der möglicherweise in C ++ geschrieben werden könnte, kann man nur sehr wenig sagen, aber es gibt einige Dinge. Das Wichtigste unter ihnen ist, dass konstruierte Objekte (dh für die der Konstruktor durch Rückkehr erfolgreich war) zerstört werden. Sie können diese Nachbedingung nicht ändern, da die Sprache davon ausgeht, dass sie wahr ist, und Destruktoren automatisch aufruft. (Technisch gesehen können Sie die Möglichkeit eines undefinierten Verhaltens akzeptieren, für das die Sprache keine Garantie für irgendetwas gibt, aber das wird wahrscheinlich an anderer Stelle besser behandelt.)

Die einzige Alternative zum Auslösen einer Ausnahme, wenn ein Konstruktor nicht erfolgreich sein kann, besteht darin, die grundlegende Definition der Klasse (die "Klasseninvariante") so zu ändern, dass gültige "Null" - oder Zombie-Zustände zulässig sind und der Konstruktor somit "erfolgreich" ist, indem ein Zombie erstellt wird .

Zombie Beispiel

Ein Beispiel für diese Zombie-Modifikation ist std :: ifstream . Sie müssen immer den Status überprüfen, bevor Sie sie verwenden können. Da dies beispielsweise bei std :: string nicht der Fall ist, ist Ihnen immer garantiert, dass Sie es unmittelbar nach der Erstellung verwenden können. Stellen Sie sich vor, Sie müssten Code wie dieses Beispiel schreiben und hätten vergessen, nach dem Zombie-Status zu suchen, würden entweder stillschweigend falsche Ergebnisse erhalten oder andere Teile Ihres Programms beschädigen:

string s = "abc";
if (s.memory_allocation_succeeded()) {
  do_something_with(s); // etc.
}

Auch die Benennung dieser Methode ist ein gutes Beispiel dafür , wie Sie die Klasse invariant und Schnittstelle für eine Situation ändern muss Zeichenfolge weder vorhersagen noch verarbeiten kann sich.

Eingabebeispiel validieren

Lassen Sie uns ein allgemeines Beispiel ansprechen: Validieren von Benutzereingaben. Nur weil wir fehlgeschlagene Eingaben zulassen möchten, bedeutet dies nicht, dass die Analysefunktion dies in ihre Nachbedingung aufnehmen muss. Dies bedeutet jedoch, dass unser Handler überprüfen muss, ob der Parser fehlschlägt.

// boost::lexical_cast<int>() is the parsing function here
void show_square() {
  using namespace std;
  assert(cin); // precondition for show_square()
  cout << "Enter a number: ";
  string line;
  if (!getline(cin, line)) { // EOF on cin
    // error handling omitted, that EOF will not be reached is considered
    // part of the precondition for this function for the sake of example
    //
    // note: the below Python version throws an EOFError from raw_input
    //  in this case, and handling this situation is the only difference
    //  between the two
  }
  int n;
  try {
    n = boost::lexical_cast<int>(line);
    // lexical_cast returns an int
    // if line == "abc", it obviously cannot meet that postcondition
  }
  catch (boost::bad_lexical_cast&) {
    cout << "I can't do that, Dave.\n";
    return;
  }
  cout << n * n << '\n';
}

Leider zeigt dies zwei Beispiele dafür, wie Sie für das Scoping von C ++ RAII / SBRM brechen müssen. Ein Beispiel in Python, das dieses Problem nicht hat und etwas zeigt, das ich mir für C ++ gewünscht habe - try-else:

# int() is the parsing "function" here
def show_square():
  line = raw_input("Enter a number: ") # same precondition as above
  # however, here raw_input will throw an exception instead of us
  # using assert
  try:
    n = int(line)
  except ValueError:
    print "I can't do that, Dave."
  else:
    print n * n

Voraussetzungen

Die Voraussetzungen müssen nicht unbedingt überprüft werden - eine Verletzung zeigt immer einen logischen Fehler an und liegt in der Verantwortung des Anrufers. Wenn Sie sie jedoch überprüfen, ist es angemessen, eine Ausnahme auszulösen. (In einigen Fällen ist es besser, Müll zurückzugeben oder das Programm zum Absturz zu bringen, obwohl diese Aktionen in anderen Kontexten schrecklich falsch sein können. Wie man am besten mit undefiniertem Verhalten umgeht, ist ein anderes Thema.)

Vergleichen Sie insbesondere die Zweige std :: logic_error und std :: runtime_error der Ausnahmehierarchie stdlib. Ersteres wird häufig für Verstöße gegen Vorbedingungen verwendet, während letzteres eher für Verstöße gegen Vorbedingungen geeignet ist.


5
Anständige Antwort, aber Sie geben im Grunde nur eine Regel an und liefern keine Begründung. Ist Ihr dann rational: Konvention und Stil?
Catskul

6
+1. Auf diese Weise wurden Ausnahmen für die Verwendung entwickelt, und ihre Verwendung auf diese Weise verbessert tatsächlich die Lesbarkeit und Wiederverwendbarkeit von Code (IMHO).
Daniel Pryden

15
Catskul: Das erste ist die Definition außergewöhnlicher Umstände, weder Begründung noch Konvention. Wenn Sie keine Nachbedingungen garantieren können , können Sie nicht zurückkehren . Ohne Ausnahmen müssen Sie die Nachbedingung extrem breit machen, um alle Fehlerzustände einzuschließen, bis zu dem Punkt, an dem sie praktisch unbrauchbar ist. Es sieht so aus, als hätte ich Ihre wörtliche Frage "Warum sollten sie selten sein" nicht beantwortet, weil ich sie nicht so betrachte. Sie sind ein Werkzeug, mit dem ich die Nachbedingungen sinnvoll verschärfen kann, während Fehler auftreten können (..in Ausnahmefällen :).

9
+1. Pre / Post-Bedingung ist die beste Erklärung, die ich je gehört habe.
KitsuneYMG

6
Viele Leute sagen "aber das läuft nur auf Konvention / Semantik hinaus." Ja das ist richtig. Konventionen und Semantik sind jedoch wichtig, da sie tiefgreifende Auswirkungen auf Komplexität, Benutzerfreundlichkeit und Wartbarkeit haben. Ist die Entscheidung, ob Vor- und Nachbedingungen formal definiert werden sollen, nicht auch nur eine Frage der Konvention und der Semantik? Und dennoch kann ihre Verwendung die Verwendung und Wartung Ihres Codes erheblich vereinfachen.
Darryl

40
  1. Teure
    Kernel-Aufrufe (oder andere System-API-Aufrufe) zur Verwaltung von Kernel (System) -Signalschnittstellen
  2. Schwer zu analysieren
    Viele der Probleme dergotoAussage gelten für Ausnahmen. Sie springen häufig in mehreren Routinen und Quelldateien über potenziell große Codemengen. Dies ist nicht immer aus dem Lesen des Zwischenquellcodes ersichtlich. (Es ist in Java.)
  3. Nicht immer vom Zwischencode vorweggenommen
    Der Code, über den gesprungen wird, wurde möglicherweise mit der Möglichkeit eines Ausnahme-Exits geschrieben oder nicht. Wenn dies ursprünglich so geschrieben wurde, wurde es möglicherweise nicht in diesem Sinne beibehalten. Denken Sie: Speicherlecks, Dateideskriptorlecks, Socketlecks, wer weiß?
  4. Wartungskomplikationen
    Es ist schwieriger, Code zu warten, der die Verarbeitung von Ausnahmen umgeht.

24
+1, gute Gründe im Allgemeinen. Beachten Sie jedoch, dass die Kosten für das Abwickeln des Stapels und das Aufrufen von Destruktoren (zumindest) durch einen funktional äquivalenten Fehlerbehandlungsmechanismus bezahlt werden, z. B. durch Testen und Zurückgeben von Fehlercodes wie in C.
j_random_hacker

Ich werde dies als Antwort markieren, obwohl ich mit j_random einverstanden bin. Das Abwickeln des Stapels und die Zuweisung / Zuweisung von Ressourcen wären in jedem funktional äquivalenten Mechanismus gleich. Wenn Sie damit einverstanden sind, hacken Sie diese einfach von der Liste, um die Antwort ein wenig besser zu machen.
Catskul

14
Julien: Der Kommentar von j_random weist darauf hin, dass es keine vergleichbaren Einsparungen gibt: int f() { char* s = malloc(...); if (some_func() == error) { free(s); return error; } ... }Sie müssen zahlen, um den Stapel abzuwickeln, egal ob Sie dies manuell oder durch Ausnahmen tun. Sie können nicht vergleichen Umgang mit Ausnahmen ohne Fehler mit überhaupt .

14
1. Teuer: Vorzeitige Optimierung ist die Wurzel allen Übels. 2. Schwer zu analysieren: im Vergleich zu verschachtelten Schichten von Fehlerrückgabecode? Ich bin respektvoll anderer Meinung. 3. Nicht immer vom Zwischencode vorweggenommen: Im Vergleich zu verschachtelten Ebenen, die Fehler nicht immer angemessen behandeln und übersetzen? Neulinge scheitern an beiden. 4. Wartungskomplikationen: wie? Sind durch Fehlercodes vermittelte Abhängigkeiten einfacher zu pflegen? ... aber dies scheint einer der Bereiche zu sein, in denen Entwickler beider Schulen Schwierigkeiten haben, die Argumente der anderen zu akzeptieren. Wie bei allem ist das Design der Fehlerbehandlung ein Kompromiss.
Pontus Gagge

1
Ich würde zustimmen, dass dies eine komplexe Angelegenheit ist und dass es schwierig ist, mit allgemeinen Worten genau zu antworten. Beachten Sie jedoch, dass in der ursprünglichen Frage lediglich gefragt wurde, warum der Anti-Ausnahme-Rat gegeben wurde, und nicht, um die allgemeinen Best Practices zu erläutern.
DigitalRoss

22

Das Auslösen einer Ausnahme ähnelt in gewissem Maße einer goto-Anweisung. Wenn Sie dies zur Flusskontrolle tun, erhalten Sie einen unverständlichen Spaghetti-Code. Schlimmer noch, in einigen Fällen wissen Sie nicht einmal, wohin der Sprung genau führt (dh wenn Sie die Ausnahme im angegebenen Kontext nicht abfangen). Dies verstößt offensichtlich gegen das Prinzip der "geringsten Überraschung", das die Wartbarkeit verbessert.


5
Wie ist es möglich, eine Ausnahme für einen anderen Zweck als die Flusskontrolle zu verwenden? Selbst in einer Ausnahmesituation handelt es sich immer noch um einen Flusskontrollmechanismus mit Konsequenzen für die Klarheit Ihres Codes (hier angenommen: unverständlich). Ich nehme an, Sie könnten Ausnahmen verwenden, aber verbieten catch, obwohl Sie sich in diesem Fall fast genauso gut std::terminate()selbst nennen könnten . Insgesamt scheint mir dieses Argument zu sagen, dass "niemals Ausnahmen verwenden" und nicht "nur selten Ausnahmen verwenden".
Steve Jessop

5
Die return-Anweisung in der Funktion führt Sie aus der Funktion heraus. Ausnahmen führen Sie, wer weiß, wie viele Ebenen sich befinden - es gibt keinen einfachen Weg, dies herauszufinden.

2
Steve: Ich verstehe deinen Standpunkt, aber das habe ich nicht gemeint. Ausnahmen sind für unerwartete Situationen in Ordnung. Einige Leute missbrauchen sie als "frühe Rückkehr", vielleicht sogar als eine Art switch-Anweisung.
Erich Kitzmüller

2
Ich denke, diese Frage geht davon aus, dass "unerwartet" nicht erklärend genug ist. Ich stimme bis zu einem gewissen Grad zu. Wenn eine Bedingung verhindert, dass eine Funktion wie gewünscht ausgeführt wird, behandelt Ihr Programm diese Situation entweder korrekt oder nicht. Wenn es damit umgeht, wird es "erwartet" und der Code muss verständlich sein. Wenn es nicht richtig damit umgeht, sind Sie in Schwierigkeiten. Sofern Sie nicht zulassen, dass die Ausnahme Ihr Programm beendet, muss eine bestimmte Ebene Ihres Codes dies "erwarten". Und doch ist es in der Praxis, wie ich in meiner Antwort sage, eine gute Regel, obwohl sie theoretisch unbefriedigend ist.
Steve Jessop

2
Außerdem können Wrapper natürlich eine Wurffunktion in eine fehlerrückgebende Funktion umwandeln oder umgekehrt. Wenn Ihr Anrufer mit Ihrer Vorstellung von "unerwartet" nicht einverstanden ist, wird die Code-Verständlichkeit nicht unbedingt beeinträchtigt. Es könnte eine Leistung opfern, bei der sie viele Bedingungen provozieren, die Sie für "unerwartet" halten, die sie jedoch für normal und wiederherstellbar halten und die sie daher fangen und in einen Errno oder was auch immer umwandeln.
Steve Jessop

16

Ausnahmen erschweren es, über den Status Ihres Programms nachzudenken. In C ++ müssen Sie beispielsweise zusätzliche Überlegungen anstellen, um sicherzustellen, dass Ihre Funktionen stark ausnahmesicher sind, als wenn Sie dies nicht tun müssten.

Der Grund ist, dass ein Funktionsaufruf ausnahmslos entweder zurückkehren oder das Programm zuerst beenden kann. Mit Ausnahmen kann ein Funktionsaufruf entweder zurückkehren oder das Programm beenden oder irgendwo zu einem catch-Block springen. Sie können dem Kontrollfluss also nicht mehr nur folgen, indem Sie sich den Code vor Ihnen ansehen. Sie müssen wissen, ob die aufgerufenen Funktionen werfen können. Möglicherweise müssen Sie wissen, was geworfen werden kann und wo es gefangen wird, je nachdem, ob es Ihnen wichtig ist, wohin die Kontrolle geht, oder nur, ob sie den aktuellen Bereich verlässt.

Aus diesem Grund sagen die Leute "benutze keine Ausnahmen, es sei denn, die Situation ist wirklich außergewöhnlich". Wenn Sie darauf eingehen, bedeutet "wirklich außergewöhnlich" "eine Situation, in der die Vorteile der Behandlung mit einem Fehlerrückgabewert durch die Kosten aufgewogen werden". Also ja, dies ist so etwas wie eine leere Aussage, obwohl, sobald Sie einige Instinkte für "wirklich außergewöhnlich" haben, es eine gute Faustregel wird. Wenn Menschen über Flusskontrolle sprechen, bedeutet dies, dass die Fähigkeit, lokal zu argumentieren (ohne Bezug auf Fangblöcke), ein Vorteil der Rückgabewerte ist.

Java hat eine breitere Definition von "wirklich außergewöhnlich" als C ++. C ++ - Programmierer möchten eher den Rückgabewert einer Funktion anzeigen als Java-Programmierer. In Java bedeutet "wirklich außergewöhnlich" möglicherweise "Ich kann aufgrund dieser Funktion kein Nicht-Null-Objekt zurückgeben". In C ++ bedeutet dies eher "Ich bezweifle sehr, dass mein Anrufer weitermachen kann". Ein Java-Stream wird also ausgelöst, wenn er eine Datei nicht lesen kann, während ein C ++ - Stream (standardmäßig) einen Fehlerwert zurückgibt. In allen Fällen kommt es jedoch darauf an, welchen Code Sie bereit sind, Ihren Anrufer zum Schreiben zu zwingen. Es ist also in der Tat eine Frage des Codierungsstils: Sie müssen einen Konsens darüber erzielen, wie Ihr Code aussehen soll und wie viel "Fehlerprüfungs" -Code Sie gegen wie viel "Ausnahmesicherheit" schreiben möchten.

Der breite Konsens über alle Sprachen hinweg scheint zu sein, dass dies am besten in Bezug auf die Wahrscheinlichkeit der Behebung des Fehlers erreicht werden kann (da nicht behebbare Fehler mit Ausnahme zu keinem Code führen, aber dennoch eine Überprüfung und Rückgabe Ihres eigenen erforderlich sind). Fehler im Code, der Fehlerrückgaben verwendet). Die Leute erwarten also, dass "diese Funktion, die ich aufrufe, eine Ausnahme auslöst" bedeutet, dass ich nicht weitermachen kann, nicht nur " es "kann nicht weitermachen ". Das ist nicht mit Ausnahmen verbunden, es ist nur ein Brauch, aber wie jede gute Programmierpraxis ist es ein Brauch, der von klugen Leuten befürwortet wird, die es anders versucht haben und die Ergebnisse nicht genossen haben. Auch ich hatte schlechte Ergebnisse Erfahrungen mit zu vielen Ausnahmen. Ich persönlich denke also an "wirklich außergewöhnlich", es sei denn, etwas an der Situation macht eine Ausnahme besonders attraktiv.

Übrigens gibt es neben der Überlegung über den Status Ihres Codes auch Auswirkungen auf die Leistung. Ausnahmen sind jetzt normalerweise billig, in Sprachen, in denen Sie berechtigt sind, sich um die Leistung zu kümmern. Sie können schneller sein als mehrere Ebenen von "Oh, das Ergebnis ist ein Fehler, dann beende ich mich am besten auch mit einem Fehler". In den schlechten alten Zeiten gab es echte Befürchtungen, dass das Auslösen einer Ausnahme, das Fangen und das Fortfahren mit der nächsten Sache das, was Sie tun, so langsam machen würde, dass es nutzlos wird. In diesem Fall bedeutet "wirklich außergewöhnlich" "die Situation ist so schlecht, dass eine schreckliche Leistung keine Rolle mehr spielt". Dies ist nicht mehr der Fall (obwohl eine Ausnahme in einer engen Schleife immer noch erkennbar ist) und zeigt hoffentlich, warum die Definition von "wirklich außergewöhnlich" flexibel sein muss.


2
"In C ++ müssen Sie beispielsweise zusätzliche Überlegungen anstellen, um sicherzustellen, dass Ihre Funktionen stark ausnahmesicher sind, als wenn Sie dies nicht tun müssten." - Angesichts der Tatsache, dass grundlegende C ++ - Konstrukte (wie z. B. new) und die Standardbibliothek alle Ausnahmen auslösen, sehe ich nicht, wie die Nichtverwendung von Ausnahmen in Ihrem Code Sie davon abhält, ausnahmsicheren Code zu schreiben, unabhängig davon.
Pavel Minaev

1
Sie müssten Google fragen. Aber ja, wenn Ihre Funktion nicht schlecht ist, müssen Ihre Anrufer einige Arbeiten ausführen, und das Hinzufügen zusätzlicher Bedingungen, die Ausnahmen verursachen, macht sie nicht "ausnahmefreudiger". Aber zu wenig Speicher ist sowieso ein Sonderfall. Es ist üblich, Programme zu haben, die nicht damit umgehen, und stattdessen einfach herunterzufahren. Sie könnten einen Codierungsstandard haben, der besagt: "Verwenden Sie keine Ausnahmen, geben Sie keine Ausnahmegarantien. Wenn neue Würfe ausgeführt werden, werden wir beendet und Compiler verwendet, die den Stapel nicht abwickeln." Kein High-Concept, aber es würde fliegen.
Steve Jessop

Sobald Sie einen Compiler verwenden, der den Stapel nicht abwickelt (oder auf andere Weise Ausnahmen deaktiviert), ist er technisch gesehen nicht mehr C ++ :)
Pavel Minaev

Wickelt den Stapel nicht mit einer Ausnahme ab, meine ich.
Steve Jessop

Woher weiß es, dass es nicht gefangen ist, es sei denn, es wickelt den Stapel ab, um einen Fangblock zu finden?
jmucchiello

11

Es gibt wirklich keinen Konsens. Das ganze Problem ist etwas subjektiv, da die "Angemessenheit" des Auslösens einer Ausnahme häufig durch bestehende Praktiken in der Standardbibliothek der Sprache selbst nahegelegt wird. Die C ++ - Standardbibliothek löst viel seltener Ausnahmen aus als beispielsweise die Java-Standardbibliothek, die fast immer Ausnahmen bevorzugt, selbst bei erwarteten Fehlern wie ungültigen Benutzereingaben (z Scanner.nextInt. B. ). Dies hat meines Erachtens einen erheblichen Einfluss auf die Meinung der Entwickler, wann es angebracht ist, eine Ausnahme auszulösen.

Als C ++ - Programmierer bevorzuge ich es persönlich, Ausnahmen für sehr "außergewöhnliche" Umstände zu reservieren, z. B. nicht genügend Speicher, Speicherplatz, Apokalypse usw. Aber ich bestehe nicht darauf, dass dies der absolut richtige Weg ist Dinge.


2
Ich denke, es gibt eine Art Konsens, aber vielleicht basiert der Konsens eher auf Konventionen als auf fundierten Argumenten. Es mag gute Gründe geben, Ausnahmen nur für außergewöhnliche Umstände zu verwenden, aber die meisten Entwickler sind sich der Gründe nicht wirklich bewusst.
Qwertie

4
Einverstanden - Sie hören oft von "Ausnahmen sind für außergewöhnliche Umstände", aber niemand stört sich daran, richtig zu definieren, was eine "Ausnahmesituation" ist - es ist größtenteils nur benutzerdefiniert und definitiv sprachspezifisch. Heck, in Python verwenden Iteratoren eine Ausnahme, um das Ende der Sequenz zu signalisieren, und dies wird als völlig normal angesehen!
Pavel Minaev

2
+1. Es gibt keine festen Regeln, nur Konventionen - aber es ist nützlich, sich an die Konventionen anderer zu halten, die Ihre Sprache verwenden, da es für Programmierer einfacher ist, den Code der anderen zu verstehen.
j_random_hacker

1
"Die Apokalypse ist passiert" - egal Ausnahmen, wenn irgendetwas UB rechtfertigt, tut das ;-)
Steve Jessop

3
Catskul: Fast die gesamte Programmierung ist konventionell. Technisch brauchen wir nicht einmal Ausnahmen oder wirklich viel. Wenn es sich nicht um NP-Vollständigkeit, Big-O / Theta / Little-O oder Universal Turing Machines handelt, handelt es sich wahrscheinlich um eine Konvention. :-)
Ken

7

Ich denke nicht, dass Ausnahmen selten verwendet werden sollten. Aber.

Nicht alle Teams und Projekte sind bereit, Ausnahmen zu verwenden. Die Verwendung von Ausnahmen erfordert eine hohe Qualifikation der Programmierer, spezielle Techniken und das Fehlen eines großen, nicht ausnahmesicheren Legacy-Codes. Wenn Sie eine riesige alte Codebasis haben, ist diese fast immer nicht ausnahmesicher. Ich bin sicher, dass Sie es nicht umschreiben wollen.

Wenn Sie Ausnahmen ausgiebig verwenden, dann:

  • Seien Sie bereit, Ihren Mitarbeitern beizubringen, was Ausnahmesicherheit ist
  • Sie sollten keine Raw-Speicherverwaltung verwenden
  • Verwenden Sie RAII ausgiebig

Andererseits kann die Verwendung von Ausnahmen in neuen Projekten mit einem starken Team den Code sauberer, einfacher zu warten und noch schneller machen:

  • Sie werden Fehler nicht verpassen oder ignorieren
  • Sie müssen diese Überprüfungen von Rückkehrcodes nicht schreiben, ohne zu wissen, was mit falschem Code auf niedriger Ebene zu tun ist
  • Wenn Sie gezwungen sind, ausnahmesicheren Code zu schreiben, wird dieser strukturierter

1
Besonders gut hat mir Ihre Erwähnung gefallen, dass "nicht alle Teams ... bereit sind, Ausnahmen zu verwenden". Ausnahmen sind definitiv etwas, das einfach zu tun scheint , aber extrem schwer richtig zu machen ist , und das ist ein Teil dessen, was sie gefährlich macht.
j_random_hacker

1
+1 für "Wenn Sie gezwungen sind, ausnahmesicheren Code zu schreiben, wird dieser strukturierter". Ausnahmebasierte Code wird gezwungen mehr Struktur zu haben, und ich finde es eigentlich viel einfacher zu Grunde über Objekte und Invarianten , wenn es unmöglich ist , sie zu ignorieren. Tatsächlich glaube ich, dass es bei starker Ausnahmesicherheit darum geht, nahezu reversiblen Code zu schreiben, was es wirklich einfach macht, einen unbestimmten Zustand zu vermeiden.
Tom

7

EDIT 20.11.2009 :

Ich habe gerade diesen MSDN-Artikel über die Verbesserung der Leistung von verwaltetem Code gelesen und dieser Teil hat mich an diese Frage erinnert:

Die Leistungskosten für das Auslösen einer Ausnahme sind erheblich. Obwohl die strukturierte Ausnahmebehandlung die empfohlene Methode zur Behandlung von Fehlerbedingungen ist, stellen Sie sicher, dass Sie Ausnahmen nur in Ausnahmefällen verwenden, wenn Fehlerbedingungen auftreten. Verwenden Sie keine Ausnahmen für den regulären Kontrollfluss.

Dies gilt natürlich nur für .NET und richtet sich auch speziell an diejenigen, die Hochleistungsanwendungen entwickeln (wie ich). Es ist also offensichtlich keine universelle Wahrheit. Trotzdem gibt es viele von uns .NET-Entwicklern, daher war es meiner Meinung nach erwähnenswert.

EDIT :

OK, zunächst einmal wollen wir eines klarstellen: Ich habe nicht die Absicht, mich mit irgendjemandem über die Leistungsfrage zu streiten. Im Allgemeinen neige ich dazu, denen zuzustimmen, die vorzeitige Optimierung für eine Sünde halten. Lassen Sie mich jedoch nur zwei Punkte ansprechen:

  1. Das Poster fordert eine objektive Begründung für die konventionelle Weisheit, dass Ausnahmen sparsam verwendet werden sollten. Wir können die Lesbarkeit und das richtige Design besprechen, was wir wollen. Aber dies sind subjektive Angelegenheiten mit Menschen, die bereit sind, auf beiden Seiten zu streiten. Ich denke, das Plakat ist sich dessen bewusst. Tatsache ist, dass die Verwendung von Ausnahmen zur Steuerung des Programmflusses häufig eine ineffiziente Vorgehensweise ist. Nein, nicht immer , aber oft . Aus diesem Grund ist es vernünftig, Ausnahmen sparsam zu verwenden, genauso wie es ein guter Rat ist, rotes Fleisch zu essen oder Wein sparsam zu trinken.

  2. Es gibt einen Unterschied zwischen der Optimierung ohne guten Grund und dem Schreiben von effizientem Code. Die Folge davon ist, dass es einen Unterschied gibt zwischen dem Schreiben von etwas Robustem, wenn nicht Optimiertem, und etwas, das einfach ineffizient ist. Manchmal denke ich, wenn Leute über Dinge wie Ausnahmebehandlung streiten, reden sie wirklich nur aneinander vorbei, weil sie grundlegend unterschiedliche Dinge diskutieren.

Betrachten Sie zur Veranschaulichung meines Standpunkts die folgenden C # -Codebeispiele.

Beispiel 1: Erkennen ungültiger Benutzereingaben

Dies ist ein Beispiel dafür , was ich Ausnahme nennen würde Missbrauch .

int value = -1;
string input = GetInput();
bool inputChecksOut = false;

while (!inputChecksOut) {
    try {
        value = int.Parse(input);
        inputChecksOut = true;

    } catch (FormatException) {
        input = GetInput();
    }
}

Dieser Code ist für mich lächerlich. Natürlich funktioniert es . Niemand argumentiert damit. Aber es sollte so etwas sein wie:

int value = -1;
string input = GetInput();

while (!int.TryParse(input, out value)) {
    input = GetInput();
}

Beispiel 2: Überprüfen, ob eine Datei vorhanden ist

Ich denke, dieses Szenario ist tatsächlich sehr verbreitet. Es scheint für viele Leute viel "akzeptabler" zu sein, da es sich um Datei-E / A handelt:

string text = null;
string path = GetInput();
bool inputChecksOut = false;

while (!inputChecksOut) {
    try {
        using (FileStream fs = new FileStream(path, FileMode.Open)) {
            using (StreamReader sr = new StreamReader(fs)) {
                text = sr.ReadToEnd();
            }
        }

        inputChecksOut = true;

    } catch (FileNotFoundException) {
        path = GetInput();
    }
}

Das scheint vernünftig genug, oder? Wir versuchen eine Datei zu öffnen. Wenn es nicht da ist, fangen wir diese Ausnahme ab und versuchen, eine andere Datei zu öffnen ... Was ist daran falsch?

Nichts wirklich. Aber betrachten Sie diese Alternative, die nicht nicht alle Ausnahmen werfen:

string text = null;
string path = GetInput();

while (!File.Exists(path)) path = GetInput();

using (FileStream fs = new FileStream(path, FileMode.Open)) {
    using (StreamReader sr = new StreamReader(fs)) {
        text = sr.ReadToEnd();
    }
}

Wenn die Leistung dieser beiden Ansätze tatsächlich gleich wäre, wäre dies natürlich nur eine Lehrfrage. Schauen wir uns das an. Für das erste Codebeispiel habe ich eine Liste mit 10000 zufälligen Zeichenfolgen erstellt, von denen keine eine richtige Ganzzahl darstellt, und dann ganz am Ende eine gültige Ganzzahlzeichenfolge hinzugefügt. Unter Verwendung der beiden oben genannten Ansätze waren dies meine Ergebnisse:

Verwendung try/ catchBlock: 25,455 Sekunden
Verwendung int.TryParse: 1,637 Millisekunden

Für das zweite Beispiel habe ich im Grunde das Gleiche getan: Ich habe eine Liste mit 10000 zufälligen Zeichenfolgen erstellt, von denen keine ein gültiger Pfad war, und dann ganz am Ende einen gültigen Pfad hinzugefügt. Dies waren die Ergebnisse:

Verwendung try/ catchBlock: 29,989 Sekunden
Verwendung File.Exists: 22,820 Millisekunden

Viele Leute antworteten darauf mit den Worten: "Ja, es ist äußerst unrealistisch, 10.000 Ausnahmen zu werfen und zu fangen. Dies übertreibt die Ergebnisse." Natürlich tut es das. Der Unterschied zwischen dem Auslösen einer Ausnahme und dem eigenen Umgang mit schlechten Eingaben wird für den Benutzer nicht erkennbar sein. Es bleibt die Tatsache, dass die Verwendung von Ausnahmen in diesen beiden Fällen 1.000- bis über 10.000-mal langsamer ist als die alternativen Ansätze, die genauso lesbar sind - wenn nicht sogar mehr.

Deshalb habe ich das folgende Beispiel für die GetNine()Methode aufgenommen. Es ist nicht so, dass es unerträglich langsam oder inakzeptabel langsam ist; es ist, dass es langsamer ist als es sein sollte ... ohne guten Grund .

Auch dies sind nur zwei Beispiele. Von Natürlich wird es Zeiten geben , wenn die Leistungseinbußen Ausnahmen nicht mit diesem schweren (Pavel Recht, denn immerhin ist es an der Umsetzung abhängen) sind. Ich sage nur: Stellen wir uns den Tatsachen, Leute - in Fällen wie dem oben genannten ist das Werfen und Fangen einer Ausnahme analog zu GetNine(); Es ist nur eine ineffiziente Art, etwas zu tun , das leicht besser gemacht werden könnte .


Sie fragen nach einer Begründung, als ob dies eine dieser Situationen wäre, in denen jeder auf einen Zug gesprungen ist, ohne zu wissen warum. Aber in der Tat ist die Antwort offensichtlich, und ich denke, Sie wissen es bereits. Die Ausnahmebehandlung hat eine schreckliche Leistung.

OK, vielleicht ist es in Ordnung für Ihr spezielles Geschäftsszenario, aber relativ gesehen bedeutet das Auslösen / Abfangen einer Ausnahme viel mehr Overhead als in vielen, vielen Fällen erforderlich ist. Sie wissen es, ich weiß es: die meiste Zeit , wenn Sie Ausnahmen von Steuerprogrammablauf verwenden, sind Sie nur langsam Code zu schreiben.

Sie könnten genauso gut fragen: Warum ist dieser Code schlecht?

private int GetNine() {
    for (int i = 0; i < 10; i++) {
        if (i == 9) return i;
    }
}

Ich wette, wenn Sie diese Funktion profilieren, werden Sie feststellen, dass sie für Ihre typische Geschäftsanwendung recht akzeptabel schnell ausgeführt wird. Das ändert nichts an der Tatsache, dass es ein schrecklich ineffizienter Weg ist, etwas zu erreichen, das viel besser gemacht werden könnte.

Das ist es, was die Leute meinen, wenn sie von Ausnahme "Missbrauch" sprechen.


2
"Ausnahmebehandlung hat eine schreckliche Leistung." - Es ist ein Implementierungsdetail und gilt nicht für alle Sprachen und sogar für C ++ speziell für alle Implementierungen.
Pavel Minaev

"Es ist ein Implementierungsdetail" Ich kann mich nicht erinnern, wie oft ich dieses Argument gehört habe - und es stinkt nur. Bitte denken Sie daran: Bei allen Computer-Dingen geht es um eine Summe von Implementierungsdetails. Und außerdem: Es ist nicht ratsam, einen Schraubenzieher als Ersatz für einen Hammer zu verwenden - genau das tun die Leute, die viel über "nur Implementierungsdetails" sprechen.
Jürgen

2
Und dennoch verwendet Python gerne Ausnahmen für die generische Flusskontrolle, da der Perf-Hit für ihre Implementierung nirgendwo so schlecht ist. Also, wenn Ihr Hammer wie ein Schraubenzieher aussieht, dann beschuldigen Sie den Hammer ...
Pavel Minaev

1
@j_random_hacker: Du hast recht, GetNinemacht seinen Job gut. Mein Punkt ist, dass es keinen Grund gibt, es zu benutzen. Es ist nicht so, als wäre es der "schnelle und einfache" Weg, etwas zu erledigen, während der "korrektere" Ansatz viel mehr Aufwand erfordern würde. Nach meiner Erfahrung ist es oft so, dass der "richtige" Ansatz tatsächlich weniger Aufwand erfordert oder ungefähr gleich ist. Auf jeden Fall ist für mich die Leistung der einzige objektive Grund, warum von der Verwendung von Ausnahmen links und rechts abgeraten wird. Ob der Leistungsverlust "stark überschätzt" wird, ist in der Tat subjektiv.
Dan Tao

1
@j_random_hacker: Ihre Seite ist sehr interessant und bringt Pavel wirklich auf den Punkt, was die Leistung je nach Implementierung betrifft. Eine Sache, an der ich sehr zweifeln würde, ist, dass jemand ein Szenario finden könnte, in dem die Verwendung von Ausnahmen die Alternative tatsächlich übertrifft (wo zumindest eine Alternative existiert, wie in meinen Beispielen).
Dan Tao

6

Alle Faustregeln für Ausnahmen sind subjektiv. Sie sollten nicht erwarten, dass Sie genau definieren, wann Sie sie verwenden und wann nicht. "Nur in Ausnahmefällen". Schöne zirkuläre Definition: Ausnahmen gelten für außergewöhnliche Umstände.

Wann Ausnahmen verwendet werden sollen, fällt in den gleichen Bereich wie "Woher weiß ich, ob dieser Code eine Klasse oder zwei ist?" Es ist teils ein Stilproblem, teils eine Präferenz. Ausnahmen sind ein Werkzeug. Sie können benutzt und missbraucht werden, und das Finden der Grenze zwischen den beiden ist Teil der Kunst und der Fähigkeit des Programmierens.

Es gibt viele Meinungen und Kompromisse. Finden Sie etwas, das zu Ihnen spricht, und folgen Sie ihm.


6

Es ist nicht so, dass Ausnahmen selten verwendet werden sollten. Es ist nur so, dass sie nur in Ausnahmefällen geworfen werden sollten. Wenn ein Benutzer beispielsweise das falsche Kennwort eingibt, ist dies keine Ausnahme.

Der Grund ist einfach: Ausnahmen verlassen eine Funktion abrupt und geben den Stapel an einen catchBlock weiter. Dieser Prozess ist sehr rechenintensiv: C ++ baut sein Ausnahmesystem so auf, dass es bei "normalen" Funktionsaufrufen nur wenig Aufwand verursacht. Wenn also eine Ausnahme ausgelöst wird, muss es viel Arbeit leisten, um herauszufinden, wohin es gehen soll. Darüber hinaus könnte jede Codezeile möglicherweise eine Ausnahme auslösen. Wenn wir eine Funktion haben f, die häufig Ausnahmen auslöst, müssen wir jetzt darauf achten, unsere try/ catch-Blöcke bei jedem Aufruf von zu verwenden f. Das ist eine ziemlich schlechte Kopplung zwischen Schnittstelle und Implementierung.


8
Sie haben im Wesentlichen die Begründung "Sie werden aus einem bestimmten Grund Ausnahmen genannt" wiederholt. Ich suche Leute, die dies zu etwas Wesentlicherem konkretisieren.
Catskul

4
Was bedeutet "außergewöhnliche Umstände"? In der Praxis muss mein Programm häufiger mit tatsächlichen IOExceptions umgehen als mit falschen Benutzerkennwörtern. Bedeutet das, dass Sie denken, ich sollte BadUserPassword zu einer Ausnahme machen, oder dass die stdlib-Leute IOException nicht zu einer Ausnahme machen sollten? "Sehr rechenintensiv" kann nicht der eigentliche Grund sein, da ich noch nie ein Programm gesehen habe (und schon gar nicht meins), bei dem die Behandlung eines falschen Passworts über einen Kontrollmechanismus ein Leistungsengpass war.
Ken

5

Ich habe dieses Problem in einem Artikel über C ++ - Ausnahmen erwähnt .

Der relevante Teil:

Fast immer ist es eine schlechte Idee, Ausnahmen zu verwenden, um den "normalen" Fluss zu beeinflussen. Wie bereits in Abschnitt 3.1 erläutert, generieren Ausnahmen unsichtbare Codepfade. Diese Codepfade sind wahrscheinlich akzeptabel, wenn sie nur in den Fehlerbehandlungsszenarien ausgeführt werden. Wenn wir jedoch Ausnahmen für andere Zwecke verwenden, ist unsere "normale" Codeausführung in einen sichtbaren und unsichtbaren Teil unterteilt und macht es sehr schwierig, Code zu lesen, zu verstehen und zu erweitern.


1
Sie sehen, ich bin von C zu C ++ gekommen und denke, dass "Fehlerbehandlungsszenarien" normal sind. Sie werden möglicherweise nicht häufig ausgeführt, aber ich verbringe mehr Zeit damit, über sie nachzudenken als über fehlerfreie Codepfade. Auch hier stimme ich dem Gefühl im Allgemeinen zu, aber es bringt uns der Definition von "normal" nicht näher und wie wir aus der Definition von "normal" ableiten können, dass die Verwendung von Ausnahmen eine schlechte Option ist.
Steve Jessop

Ich bin auch von C zu C ++ gekommen, habe aber auch in CI zwischen "normaler" Fehlerbehandlung am Ende des Flusses unterschieden. Wenn Sie zB fopen () aufrufen, gibt es einen Zweig, der sich mit dem Erfolgsfall befasst und der "normal" ist, und einen, der sich mit möglichen Fehlerursachen befasst, und das ist die Fehlerbehandlung.
Nemanja Trifunovic

2
Richtig, aber ich werde auf mysteriöse Weise nicht schlau, wenn ich über Fehlercode nachdenke. Wenn also "unsichtbare Codepfade" für "normalen" Code sehr schwer zu lesen, zu verstehen und zu erweitern sind, sehe ich überhaupt nicht, warum die Anwendung des Wortes "Fehler" sie "wohl akzeptabel" macht. Nach meiner Erfahrung treten immer wieder Fehlersituationen auf, die sie "normal" machen. Wenn Sie Ausnahmen in Fehlersituationen herausfinden können, können Sie sie in Nicht-Fehlersituationen herausfinden. Wenn Sie nicht können, können Sie nicht und alles, was Sie getan haben, ist, Ihren Fehlercode undurchdringlich zu machen, aber diese Tatsache zu ignorieren, weil es "nur Fehler" sind.
Steve Jessop

1
Beispiel: Sie haben eine Funktion, bei der etwas erledigt werden muss, aber Sie werden eine ganze Reihe verschiedener Ansätze ausprobieren, und Sie wissen nicht, welche erfolgreich sein werden, und jeder von ihnen könnte einige zusätzliche Unteransätze ausprobieren, und so weiter, bis etwas funktioniert. Dann würde ich argumentieren, dass Misserfolg "normal" und Erfolg "außergewöhnlich" ist, obwohl dies eindeutig kein Fehler ist. Das Auslösen einer Ausnahme für den Erfolg ist in den meisten Programmen nicht schwerer zu verstehen als das Auslösen einer Ausnahme für schreckliche Fehler. Sie stellen sicher, dass nur ein Codebit wissen muss, was als Nächstes zu tun ist, und springen direkt dorthin.
Steve Jessop

1
@onebyone: Dein 2. Kommentar macht tatsächlich einen guten Punkt, den ich anderswo nicht gesehen habe. Ich denke, die Antwort darauf ist, dass (wie Sie in Ihrer eigenen Antwort effektiv gesagt haben) die Verwendung von Ausnahmen für Fehlerbedingungen in die "Standardpraktiken" vieler Sprachen aufgenommen wurde, was es zu einer nützlichen Richtlinie macht, diese auch dann einzuhalten, wenn die ursprünglichen Gründe dafür vorliegen das zu tun war falsch.
j_random_hacker

5

Mein Ansatz zur Fehlerbehandlung ist, dass es drei grundlegende Arten von Fehlern gibt:

  • Eine seltsame Situation, die an der Fehlerstelle behandelt werden kann. Dies kann der Fall sein, wenn ein Benutzer an einer Eingabeaufforderung eine ungültige Eingabe eingibt. Das richtige Verhalten besteht einfach darin, sich beim Benutzer zu beschweren und in diesem Fall eine Schleife zu erstellen. Eine andere Situation könnte eine Division durch Null sein. Diese Situationen sind keine wirklichen Fehlersituationen und werden normalerweise durch fehlerhafte Eingaben verursacht.
  • Eine Situation wie die vorherige, die jedoch an der Fehlerstelle nicht behandelt werden kann. Wenn Sie beispielsweise eine Funktion haben, die einen Dateinamen verwendet und die Datei mit diesem Namen analysiert, kann sie die Datei möglicherweise nicht öffnen. In diesem Fall kann der Fehler nicht behoben werden. Dies ist, wenn Ausnahmen leuchten. Anstatt den C-Ansatz zu verwenden (einen ungültigen Wert als Flag zurückgeben und eine globale Fehlervariable festlegen, um das Problem anzuzeigen), kann der Code stattdessen eine Ausnahme auslösen. Der aufrufende Code kann dann die Ausnahme behandeln, um beispielsweise den Benutzer zur Eingabe eines anderen Dateinamens aufzufordern.
  • Eine Situation, die nicht passieren sollte. Dies ist der Fall, wenn eine Klasseninvariante verletzt wird oder eine Funktion einen ungültigen Parameter oder dergleichen empfängt. Dies weist auf einen logischen Fehler im Code hin. Abhängig von der Fehlerstufe kann eine Ausnahme angebracht sein, oder das Erzwingen einer sofortigen Beendigung kann vorzuziehen sein (wie dies auch der assertFall ist). Im Allgemeinen weisen diese Situationen darauf hin, dass irgendwo im Code ein Fehler aufgetreten ist, und Sie können effektiv nicht darauf vertrauen, dass etwas anderes korrekt ist - es kann zu einer weit verbreiteten Speicherbeschädigung kommen. Dein Schiff sinkt, steig aus.

Um es zu paraphrasieren: Ausnahmen gelten für den Fall, dass Sie ein Problem haben, mit dem Sie sich befassen können, das Sie jedoch nicht an der Stelle lösen können, an der Sie es bemerken. Probleme, mit denen Sie sich nicht befassen können, sollten das Programm einfach beenden. Probleme, mit denen Sie sich sofort befassen können, sollten einfach behandelt werden.


2
Sie beantworten die falsche Frage. Wir möchten nicht wissen, warum wir Ausnahmen für die Behandlung von Fehlerszenarien in Betracht ziehen sollten (oder nicht). Wir möchten wissen, warum wir sie für Szenarien verwenden sollten, die keine Fehler behandeln.
j_random_hacker

5

Ich habe hier einige Antworten gelesen. Ich bin immer noch erstaunt darüber, worum es bei all dieser Verwirrung geht. Ich bin mit all diesen Ausnahmen nicht einverstanden == Spagetty-Code. Mit Verwirrung meine ich, dass es Leute gibt, die die Behandlung von C ++ - Ausnahmen nicht schätzen. Ich bin mir nicht sicher, wie ich von der Behandlung von C ++ - Ausnahmen erfahren habe - aber ich habe die Auswirkungen innerhalb von Minuten verstanden. Dies war ungefähr 1996 und ich verwendete den Borland C ++ - Compiler für OS / 2. Ich hatte nie ein Problem zu entscheiden, wann ich Ausnahmen verwenden sollte. Normalerweise verpacke ich fehlbare Aktionen zum Rückgängigmachen in C ++ - Klassen. Zu diesen Aktionen zum Rückgängigmachen gehören:

  • Erstellen / Zerstören eines Systemhandles (für Dateien, Speicherzuordnungen, WIN32-GUI-Handles, Sockets usw.)
  • Handler setzen / deaktivieren
  • Speicher zuweisen / freigeben
  • einen Mutex beanspruchen / freigeben
  • Inkrementieren / Dekrementieren eines Referenzzählers
  • Fenster ein- / ausblenden

Dann gibt es funktionale Wrapper. Um Systemaufrufe (die nicht in die vorherige Kategorie fallen) in C ++ zu verpacken. ZB Lesen / Schreiben von / in eine Datei. Wenn etwas fehlschlägt, wird eine Ausnahme ausgelöst, die vollständige Informationen zum Fehler enthält.

Dann gibt es Ausnahmen abzufangen / erneut zu werfen, um einem Fehler weitere Informationen hinzuzufügen.

Insgesamt führt die Behandlung von C ++ - Ausnahmen zu saubererem Code. Die Codemenge wird drastisch reduziert. Schließlich kann man einen Konstruktor verwenden, um fehlbare Ressourcen zuzuweisen und nach einem solchen Fehler eine korruptionsfreie Umgebung aufrechtzuerhalten.

Man kann solche Klassen zu komplexen Klassen verketten. Sobald ein Konstruktor eines Mitglieds- / Basisobjekts ausgeführt wurde, kann man sich darauf verlassen, dass alle anderen Konstruktoren desselben Objekts (zuvor ausgeführt) erfolgreich ausgeführt wurden.


3

Ausnahmen sind eine sehr ungewöhnliche Methode zur Flusskontrolle im Vergleich zu herkömmlichen Konstrukten (Schleifen, Wenns, Funktionen usw.). Die normalen Kontrollflusskonstrukte (Schleifen, Wenns, Funktionsaufrufe usw.) können alle normalen Situationen bewältigen. Wenn Sie nach einer Ausnahme für ein Routineereignis greifen, müssen Sie möglicherweise überlegen, wie Ihr Code strukturiert ist.

Es gibt jedoch bestimmte Arten von Fehlern, die mit den normalen Konstrukten nicht einfach behandelt werden können. Katastrophale Fehler (wie ein Fehler bei der Ressourcenzuweisung) können auf niedriger Ebene erkannt, dort jedoch wahrscheinlich nicht behandelt werden, sodass eine einfache if-Anweisung nicht ausreicht. Diese Arten von Fehlern müssen im Allgemeinen auf einer viel höheren Ebene behandelt werden (z. B. Speichern der Datei, Protokollieren des Fehlers, Beenden). Der Versuch, einen solchen Fehler mit herkömmlichen Methoden (wie Rückgabewerten) zu melden, ist mühsam und fehleranfällig. Darüber hinaus wird Overhead in Schichten von APIs mittlerer Ebene injiziert, um diesen bizarren, ungewöhnlichen Fehler zu beheben. Der Overhead lenkt den Client von diesen APIs ab und erfordert, dass er sich um Probleme kümmert, die außerhalb seiner Kontrolle liegen. Ausnahmen bieten eine Möglichkeit, nicht-lokale Behandlung für große Fehler durchzuführen, die '

Wenn ein Client ParseIntmit einer Zeichenfolge aufruft und die Zeichenfolge keine Ganzzahl enthält, kümmert sich der unmittelbare Aufrufer wahrscheinlich um den Fehler und weiß, was zu tun ist. Sie würden also ParseInt so gestalten, dass ein Fehlercode für so etwas zurückgegeben wird.

Wenn andererseits ein Fehler ParseIntauftritt, weil kein Puffer zugewiesen werden konnte, weil der Speicher schrecklich fragmentiert ist, weiß der Anrufer nicht, was er dagegen tun soll. Es müsste diesen ungewöhnlichen Fehler bis zu einer Schicht aufblasen, die sich mit diesen grundlegenden Fehlern befasst. Das besteuert jeden dazwischen (weil sie den Fehlerübergabemechanismus in ihren eigenen APIs berücksichtigen müssen). Eine Ausnahme ermöglicht es, diese Ebenen zu überspringen (und gleichzeitig sicherzustellen, dass die erforderliche Bereinigung erfolgt).

Wenn Sie Code auf niedriger Ebene schreiben, kann es schwierig sein, zu entscheiden, wann herkömmliche Methoden verwendet und wann Ausnahmen ausgelöst werden sollen. Der Low-Level-Code muss die Entscheidung treffen (werfen oder nicht). Aber es ist der übergeordnete Code, der wirklich weiß, was erwartet wird und was außergewöhnlich ist.


1
Und doch löst Java parseInttatsächlich eine Ausnahme aus. Daher würde ich sagen, dass Meinungen über die Angemessenheit des Auslösens von Ausnahmen in hohem Maße von bereits vorhandenen Praktiken in den Standardbibliotheken der von ihnen gewählten Entwicklungssprache abhängen.
Charles Salvia

Die Laufzeit von Java muss ohnehin die Informationen im Auge behalten, damit Ausnahmen leicht generiert werden können. C ++ - Code verfügt in der Regel nicht über diese Informationen, sodass die Generierung von Informationen einen Leistungsverlust zur Folge hat. Wenn Sie es nicht zur Hand haben, ist es in der Regel schneller / kleiner / cachefreundlicher usw. Außerdem überprüft Java-Code dynamisch Dinge wie Arrays usw., eine Funktion, die C ++ nicht eigen ist, also eine zusätzliche Java-Ausnahmeklasse, die der Entwickler ist Viele dieser Dinge werden bereits mit Try / Catch-Blöcken erledigt. Warum also nicht Ausnahmen für ALLES verwenden?
Ape-inago

1
@Charles - Die Frage ist mit C ++ markiert, also habe ich aus dieser Perspektive geantwortet. Ich habe noch nie einen Java- (oder C #) -Programmierer sagen hören, dass Ausnahmen nur für außergewöhnliche Bedingungen gelten. C ++ - Programmierer sagen dies regelmäßig, und die Frage war, warum.
Adrian McCarthy

1
Tatsächlich sagen C # -Ausnahmen dies auch aus, und der Grund dafür ist, dass das Auslösen von Ausnahmen in .NET sehr teuer ist (mehr als z. B. in Java).
Pavel Minaev

1
@Adrian: stimmt, die Frage ist mit C ++ markiert, obwohl die Philosophie der Ausnahmebehandlung eher ein sprachübergreifender Dialog ist, da sich Sprachen sicherlich gegenseitig beeinflussen. Auf jeden Fall löst Boost.lexical_cast eine Ausnahme aus. Aber ist eine ungültige Zeichenfolge wirklich so "außergewöhnlich"? Es ist wahrscheinlich nicht so, aber die Boost-Entwickler waren der Meinung, dass es sich lohnt, Ausnahmen zu verwenden, wenn man diese coole Cast-Syntax hat. Dies zeigt nur, wie subjektiv das Ganze ist.
Charles Salvia

3

In C ++ gibt es mehrere Gründe.

Erstens ist es häufig schwer zu erkennen, woher Ausnahmen kommen (da sie von fast allem ausgelöst werden können), und daher ist der catch-Block so etwas wie eine COME FROM-Anweisung. Es ist schlimmer als ein GO TO, da Sie in einem GO TO wissen, woher Sie kommen (die Anweisung, kein zufälliger Funktionsaufruf) und wohin Sie gehen (das Label). Sie sind im Grunde eine potenziell ressourcensichere Version von Cs setjmp () und longjmp (), und niemand möchte diese verwenden.

Zweitens ist in C ++ keine Garbage Collection integriert, sodass C ++ - Klassen, die Ressourcen besitzen, diese in ihren Destruktoren entfernen. Daher muss das System in der C ++ - Ausnahmebehandlung alle Destruktoren im Gültigkeitsbereich ausführen. In Sprachen mit GC und ohne echte Konstruktoren wie Java ist das Auslösen von Ausnahmen viel weniger aufwändig.

Drittens hat die C ++ - Community, einschließlich Bjarne Stroustrup und des Standards Committee sowie verschiedener Compiler-Autoren, angenommen, dass Ausnahmen außergewöhnlich sein sollten. Im Allgemeinen lohnt es sich nicht, gegen die Sprachkultur vorzugehen. Die Implementierungen basieren auf der Annahme, dass Ausnahmen selten sind. Die besseren Bücher behandeln Ausnahmen als außergewöhnlich. Guter Quellcode verwendet nur wenige Ausnahmen. Gute C ++ - Entwickler behandeln Ausnahmen als außergewöhnlich. Um dem entgegenzuwirken, möchten Sie einen guten Grund, und alle Gründe, die ich sehe, sind auf der Seite, sie außergewöhnlich zu halten.


1
"In C ++ ist keine Garbage Collection integriert, daher werden C ++ - Klassen, die Ressourcen besitzen, in ihren Destruktoren entfernt. Daher muss das System bei der C ++ - Ausnahmebehandlung alle Destruktoren im Gültigkeitsbereich ausführen. In Sprachen mit GC und ohne echte Konstruktoren Wie bei Java ist das Auslösen von Ausnahmen viel weniger belastend. " - Destruktoren müssen ausgeführt werden, wenn der Bereich normal verlassen wird, und Java- finallyBlöcke unterscheiden sich hinsichtlich des Implementierungsaufwands nicht von C ++ - Destruktoren.
Pavel Minaev

Ich mag diese Antwort, aber wie Pavel denke ich nicht, dass der zweite Punkt legitim ist. Wenn der Gültigkeitsbereich aus irgendeinem Grund endet, einschließlich anderer Arten der Fehlerbehandlung oder einfach nur der Fortsetzung des Programms, werden Destruktoren trotzdem aufgerufen.
Catskul

+1 für die Erwähnung der Sprachkultur als Grund für den Fluss. Diese Antwort ist für manche unbefriedigend, aber es ist ein echter Grund (und ich glaube, es ist der genaueste Grund).
j_random_hacker

@Pavel: Bitte ignorieren Sie meinen falschen Kommentar oben (jetzt gelöscht), der besagt, dass finallydie Ausführung von Java- Blöcken nicht garantiert ist - natürlich. Ich war verwirrt mit der Java- finalize()Methode, deren Ausführung nicht garantiert ist.
j_random_hacker

Rückblickend auf diese Antwort besteht das Problem bei Destruktoren darin, dass sie alle sofort aufgerufen werden müssen, anstatt möglicherweise bei jeder Funktionsrückgabe voneinander getrennt zu sein. Das ist immer noch kein sehr guter Grund, aber ich denke, es hat ein wenig Gültigkeit.
David Thornley

2

Dies ist ein schlechtes Beispiel für die Verwendung von Ausnahmen als Kontrollfluss:

int getTotalIncome(int incomeType) {
   int totalIncome= 0;
   try {
      totalIncome= calculateIncomeAsTypeA();
   } catch (IncorrectIncomeTypeException& e) {
      totalIncome= calculateIncomeAsTypeB();
   }

   return totalIncome;
}

Was sehr schlecht ist, aber Sie sollten schreiben:

int getTotalIncome(int incomeType) {
   int totalIncome= 0;
   if (incomeType == A) {
      totalIncome= calculateIncomeAsTypeA();
   } else if (incomeType == B) {
      totalIncome= calculateIncomeAsTypeB();
   }
   return totalIncome;
}

Dieses zweite Beispiel erfordert offensichtlich einige Umgestaltungen (wie die Verwendung der Entwurfsmusterstrategie), zeigt jedoch gut, dass Ausnahmen nicht für den Kontrollfluss gedacht sind.

Ausnahmen sind auch mit einigen Leistungseinbußen verbunden, aber Leistungsprobleme sollten der Regel folgen: "Vorzeitige Optimierung ist die Wurzel allen Übels"


Sieht aus wie ein Fall, in dem mehr Polymorphismus gut wäre, anstatt zu überprüfen incomeType.
Sarah Vessels

@ Sarah ja ich weiß es wäre gut Es dient nur zu Illustrationszwecken.
Edison Gustavo Muenz

3
Warum ist es schlecht? Warum solltest du es auf die 2. Art schreiben? Der Fragesteller möchte Gründe für Regeln kennen, keine Regeln. -1.
j_random_hacker

2
  1. Wartbarkeit: Wie von den oben genannten Personen erwähnt, ist das Werfen von Ausnahmen auf einen Hut mit der Verwendung von Gotos vergleichbar.
  2. Interoperabilität: Sie können C ++ - Bibliotheken nicht mit C / Python-Modulen verbinden (zumindest nicht einfach), wenn Sie Ausnahmen verwenden.
  3. Leistungsabfall: RTTI wird verwendet, um den Typ der Ausnahme zu ermitteln, die zusätzlichen Overhead verursacht. Ausnahmen sind daher nicht für die Behandlung häufig vorkommender Anwendungsfälle geeignet (Benutzer hat int anstelle von string usw. eingegeben).

2
1. In einigen Sprachen werden Ausnahmen jedoch auf einen Schlag geworfen. 3. Manchmal spielt Leistung keine Rolle. (80% der Zeit?)
UncleBens

2
2. C ++ - Programme verwenden fast immer Ausnahmen, da sie von so vielen Standardbibliotheken verwendet werden. Die Containerklassen werfen. Die String-Klasse wirft. Die Stream-Klassen werfen.
David Thornley

Welche Container- und String-Operationen werfen außer at()? Das heißt, jeder newkann werfen, also ...
Pavel Minaev

Soweit ich weiß, ist RTTI für Try / Throw / Catch nicht unbedingt erforderlich. Leistungsabfall wird immer erwähnt, und ich glaube es, aber niemand erwähnt jemals eine Skala oder Links zu Referenzen.
Catskul

UncleBens: (1) dient der Wartbarkeit nicht der Leistung. Übermäßige Verwendung zum Ausprobieren / Fangen / Werfen verringert die Lesbarkeit des Codes. Faustregel (und ich könnte dafür unter Beschuss geraten, aber es ist nur meine Meinung): Je weniger Ein- und Ausstiegspunkte vorhanden sind, desto einfacher ist es, den Code zu lesen. David Thornley: Nicht, wenn Sie Interoperabilität benötigen. Das Flag -fno-exception in gcc deaktiviert Ausnahmen, wenn Sie eine Bibliothek kompilieren, die aus C-Code aufgerufen wird.
Sridhar Iyer

2

Ich würde sagen, dass Ausnahmen ein Mechanismus sind, mit dem Sie auf sichere Weise aus dem aktuellen Kontext (im einfachsten Sinne aus dem aktuellen Stapelrahmen, aber es ist mehr als das) herauskommen. Es ist das, was strukturierte Programmierung einem goto am nächsten kommt. Um Ausnahmen so zu verwenden, wie sie verwendet werden sollen, müssen Sie eine Situation haben, in der Sie nicht weitermachen können, was Sie jetzt tun, und Sie können es nicht an dem Punkt behandeln, an dem Sie sich gerade befinden. Wenn beispielsweise das Kennwort des Benutzers falsch ist, können Sie fortfahren, indem Sie false zurückgeben. Wenn das UI-Subsystem jedoch meldet, dass es den Benutzer nicht einmal dazu auffordern kann, wäre es falsch, einfach "Anmeldung fehlgeschlagen" zurückzugeben. Die aktuelle Codeebene weiß einfach nicht, was zu tun ist. Daher wird ein Ausnahmemechanismus verwendet, um die Verantwortung an jemanden zu delegieren, der möglicherweise weiß, was zu tun ist.


2

Ein sehr praktischer Grund ist, dass ich beim Debuggen eines Programms häufig Ausnahmen der ersten Chance (Debug -> Ausnahmen) aktiviere, um eine Anwendung zu debuggen. Wenn es viele Ausnahmen gibt, ist es sehr schwierig herauszufinden, wo etwas "schief gelaufen" ist.

Außerdem führt es zu einigen Anti-Mustern wie dem berüchtigten "Fangwurf" und verschleiert die wirklichen Probleme. Weitere Informationen hierzu finden Sie in einem Blogbeitrag, den ich zu diesem Thema verfasst habe.


2

Ich bevorzuge es, Ausnahmen so wenig wie möglich zu verwenden. Ausnahmen zwingen den Entwickler, eine Bedingung zu behandeln, die ein echter Fehler sein kann oder nicht. Die Definition, ob es sich bei der fraglichen Ausnahme um ein schwerwiegendes Problem handelt oder um ein Problem, das sofort behoben werden muss .

Das Gegenargument dazu ist, dass nur faule Leute mehr tippen müssen, um sich in die Füße zu schießen.

Laut der Codierungsrichtlinie von Google werden niemals Ausnahmen verwendet , insbesondere in C ++. Ihre Anwendung ist entweder nicht auf Ausnahmen vorbereitet oder es ist so. Wenn dies nicht der Fall ist, wird die Ausnahme es wahrscheinlich weitergeben, bis Ihre Anwendung stirbt.

Es macht nie Spaß herauszufinden, welche Bibliothek Sie verwendet haben, um Ausnahmen zu werfen, und Sie waren nicht bereit, damit umzugehen.


1

Berechtigter Fall, um eine Ausnahme auszulösen:

  • Sie versuchen, eine Datei zu öffnen. Sie ist nicht vorhanden. Eine FileNotFoundException wird ausgelöst.

Unzulässiger Fall:

  • Sie möchten nur dann etwas tun, wenn keine Datei vorhanden ist. Sie versuchen, die Datei zu öffnen, und fügen dann dem catch-Block Code hinzu.

Ich verwende Ausnahmen, wenn ich den Fluss der Anwendung bis zu einem bestimmten Punkt unterbrechen möchte . An diesem Punkt befindet sich der Haken (...) für diese Ausnahme. Zum Beispiel ist es sehr häufig, dass wir eine Menge Projekte verarbeiten müssen und jedes Projekt unabhängig von den anderen verarbeitet werden sollte. Die Schleife, die die Projekte verarbeitet, hat also einen try ... catch-Block. Wenn während der Projektverarbeitung eine Ausnahme ausgelöst wird, wird für dieses Projekt alles zurückgesetzt, der Fehler wird protokolliert und das nächste Projekt wird verarbeitet. Das Leben geht weiter.

Ich denke, Sie sollten Ausnahmen für Dinge wie eine nicht existierende Datei, einen ungültigen Ausdruck und ähnliches verwenden. Sie sollten keine Ausnahmen für Bereichstests / Datentyptests / Dateiexistenz / was auch immer verwenden, wenn es eine einfache / kostengünstige Alternative dazu gibt. Sie sollten keine Ausnahmen für Bereichstests / Datentyptests / Dateiexistenz / was auch immer verwenden, wenn es eine einfache / kostengünstige Alternative dazu gibt, da diese Art von Logik den Code schwer verständlich macht:

RecordIterator<MyObject> ri = createRecordIterator();
try {
   MyObject myobject = ri.next();
} catch(NoSuchElement exception) {
   // Object doesn't exist, will create it
}

Das wäre besser:

RecordIterator<MyObject> ri = createRecordIterator();
if (ri.hasNext()) {
   // It exists! 
   MyObject myobject = ri.next();
} else {
   // Object doesn't exist, will create it
}

KOMMENTAR ZUR ANTWORT HINZUGEFÜGT:

Vielleicht war mein Beispiel nicht sehr gut - die ri.next () sollte im zweiten Beispiel keine Ausnahme auslösen, und wenn dies der Fall ist, gibt es etwas wirklich Außergewöhnliches und eine andere Aktion sollte woanders durchgeführt werden. Wenn das Beispiel 1 häufig verwendet wird, fangen Entwickler eine generische Ausnahme anstelle der spezifischen ab und gehen davon aus, dass die Ausnahme auf den erwarteten Fehler zurückzuführen ist, aber möglicherweise auf etwas anderes. Dies führt letztendlich dazu, dass echte Ausnahmen ignoriert werden, da Ausnahmen Teil des Anwendungsflusses wurden und keine Ausnahme.

Die Kommentare dazu können mehr als meine Antwort selbst hinzufügen.


1
Warum nicht? Warum nicht Ausnahmen für den zweiten Fall verwenden, den Sie erwähnen? Der Fragesteller möchte Gründe für Regeln kennen, keine Regeln.
j_random_hacker

Vielen Dank für die Ausarbeitung, aber meiner Meinung nach haben Ihre 2 Code-Snippets eine nahezu identische Komplexität - beide verwenden eine stark lokalisierte Steuerlogik. Wenn die Komplexität von Ausnahmen die von ihnen / dann / sonst am deutlichsten übersteigt, liegt eine Reihe von Anweisungen im try-Block vor, von denen jede eine auslösen könnte - würden Sie zustimmen?
j_random_hacker

Vielleicht war mein Beispiel nicht sehr gut - die ri.next () sollte im zweiten Beispiel keine Ausnahme auslösen, und wenn dies der Fall ist, gibt es etwas wirklich Außergewöhnliches und es sollten andere Maßnahmen ergriffen werden. Wenn das Beispiel 1 häufig verwendet wird, fangen Entwickler eine generische Ausnahme anstelle der spezifischen ab und gehen davon aus, dass die Ausnahme auf den erwarteten Fehler zurückzuführen ist, aber möglicherweise auf etwas anderes. Dies führt letztendlich dazu, dass echte Ausnahmen ignoriert werden, da Ausnahmen Teil des Anwendungsflusses wurden und keine Ausnahme.
Ravi Wallau

Sie sagen also: Mit der Zeit können sich andere Aussagen innerhalb des tryBlocks ansammeln , und dann sind Sie sich nicht mehr sicher, ob der catchBlock wirklich das fängt, von dem Sie dachten, dass er es fängt - stimmt das? Während es schwieriger ist, den if / then / else-Ansatz auf die gleiche Weise zu missbrauchen, weil Sie immer nur eine Sache gleichzeitig testen können, anstatt eine Reihe von Dingen gleichzeitig, kann der außergewöhnliche Ansatz zu einem fragileren Code führen. Wenn ja, besprechen Sie dies bitte in Ihrer Antwort und ich werde gerne +1 geben, da ich denke, dass Code-Fragilität ein guter Grund ist.
j_random_hacker

0

Grundsätzlich sind Ausnahmen eine unstrukturierte und schwer verständliche Form der Flusskontrolle. Dies ist erforderlich, wenn Fehlerbedingungen behandelt werden, die nicht Teil des normalen Programmablaufs sind, um zu vermeiden, dass die Fehlerbehandlungslogik die normale Flusssteuerung Ihres Codes zu stark beeinträchtigt.

IMHO-Ausnahmen sollten verwendet werden, wenn Sie einen vernünftigen Standard angeben möchten, falls der Anrufer das Schreiben von Fehlerbehandlungscode vernachlässigt oder wenn der Fehler am besten weiter oben im Aufrufstapel als der unmittelbare Anrufer behandelt werden kann. Die vernünftige Standardeinstellung besteht darin, das Programm mit einer angemessenen Diagnosefehlermeldung zu beenden. Die verrückte Alternative besteht darin, dass das Programm in einem fehlerhaften Zustand hinkt und abstürzt oder zu einem späteren Zeitpunkt eine schlechte Ausgabe erzeugt, die schwerer zu diagnostizieren ist. Wenn der "Fehler" ein normaler Teil des Programmablaufs ist, den der Aufrufer nicht vernünftigerweise vergessen konnte, danach zu suchen, sollten keine Ausnahmen verwendet werden.


0

Ich denke, "benutze es selten" ist nicht der richtige Satz. Ich würde es vorziehen, "nur in Ausnahmesituationen zu werfen".

Viele haben erklärt, warum Ausnahmen in normalen Situationen nicht verwendet werden sollten. Ausnahmen haben das Recht zur Fehlerbehandlung und nur zur Fehlerbehandlung.

Ich werde mich auf einen anderen Punkt konzentrieren:

Eine andere Sache ist das Leistungsproblem. Compiler hatten lange Mühe, sie schnell zu bekommen. Ich bin nicht sicher, wie der genaue Status jetzt ist, aber wenn Sie Ausnahmen für den Kontrollfluss verwenden, werden Sie ein anderes Problem bekommen: Ihr Programm wird langsam!

Der Grund ist, dass Ausnahmen nicht nur sehr mächtige goto-Anweisungen sind, sondern auch den Stapel für alle Frames abwickeln müssen, die sie verlassen. Somit müssen implizit auch die Objekte auf dem Stapel dekonstruiert werden und so weiter. Ohne sich dessen bewusst zu sein, wird ein einziger Auswurf einer Ausnahme wirklich eine ganze Reihe von Mechanikern einbeziehen. Der Prozessor muss eine Menge tun.

So werden Sie am Ende Ihren Prozessor elegant verbrennen, ohne es zu wissen.

Also: Ausnahmen nur in Ausnahmefällen verwenden - Bedeutung: Wenn echte Fehler aufgetreten sind!


1
"Der Grund ist, dass Ausnahmen nicht nur sehr mächtige goto-Anweisungen sind, sondern auch den Stapel für alle Frames abwickeln müssen, die sie verlassen. Somit müssen implizit auch die Objekte auf dem Stapel dekonstruiert werden und so weiter." - Wenn Sie mehrere Stapelrahmen auf dem Callstack nicht verfügbar sind, müssen alle diese Stapelrahmen (und Objekte darauf) ohnehin irgendwann zerstört werden. - Es spielt keine Rolle, ob dies passiert, weil Sie normal zurückkehren oder einen werfen Ausnahme. Am Ende muss man, ohne anzurufen std::terminate(), immer noch zerstören.
Pavel Minaev

Sie haben natürlich recht. Aber denken Sie trotzdem daran: Jedes Mal, wenn Sie Ausnahmen verwenden, muss das System die Infrastruktur bereitstellen, um all dies auf magische Weise zu tun. Ich wollte nur ein wenig klarstellen, dass es nicht nur ein Goto ist. Auch beim Abwickeln muss das System den richtigen Fangort finden, was zusätzliche Zeit, Code und die Verwendung von RTTI kostet. Es ist also viel mehr als ein Sprung - die meisten Leute wissen einfach nicht, dass sie das verstehen.
Jürgen

Solange Sie sich an standardkonformes C ++ halten, ist RTTI unvermeidlich. Ansonsten ist natürlich Overhead da. Es ist nur so, dass ich es oft deutlich übertrieben sehe.
Pavel Minaev

1
-1. "Ausnahmen haben das Recht auf Fehlerbehandlung und nur auf Fehlerbehandlung" - sagt wer? Warum? Leistung kann nicht der einzige Grund sein. (A) Der meiste Code der Welt befindet sich nicht in der innersten Schleife einer Spiel-Rendering-Engine oder einer Matrix-Multiplikationsfunktion, sodass die Verwendung von Ausnahmen keinen wahrnehmbaren Leistungsunterschied aufweist. (B) Wie jemand in einem Kommentar irgendwo bemerkt hat, müsste das gesamte Abwickeln des Stapels letztendlich sowieso stattfinden, selbst wenn die alte C-artige Überprüfung auf Fehler Rückgabewerte und Rückgabe bei Bedarf Fehler Handhabungsansatz wurde verwendet.
j_random_hacker

Sag ich. In diesem Thread wurden viele Gründe angeführt. Lies sie einfach. Ich wollte nur einen beschreiben. Wenn es nicht das ist, was du brauchst, beschuldige mich nicht.
Jürgen

0

Der Zweck von Ausnahmen besteht darin, Software fehlertolerant zu machen. Die Antwort auf jede von einer Funktion ausgelöste Ausnahme führt jedoch zur Unterdrückung. Ausnahmen sind nur eine formale Struktur, die Programmierer dazu zwingt anzuerkennen, dass bestimmte Dinge mit einer Routine schief gehen können und dass der Client-Programmierer diese Bedingungen kennen und sie bei Bedarf berücksichtigen muss.

Um ehrlich zu sein, sind Ausnahmen ein Problem, das den Programmiersprachen hinzugefügt wird, um Entwicklern einige formale Anforderungen zu stellen, die die Verantwortung für die Behandlung von Fehlerfällen vom unmittelbaren Entwickler auf einen zukünftigen Entwickler verlagern.

Ich glaube, dass eine gute Programmiersprache keine Ausnahmen unterstützt, wie wir sie in C ++ und Java kennen. Sie sollten sich für Programmiersprachen entscheiden, die einen alternativen Ablauf für alle Arten von Rückgabewerten von Funktionen bieten. Der Programmierer sollte dafür verantwortlich sein, alle Arten von Ausgaben einer Routine zu antizipieren und sie in einer separaten Codedatei zu verarbeiten, wenn es mir recht ist.


0

Ich benutze Ausnahmen, wenn:

  • Es ist ein Fehler aufgetreten, der nicht von lokalem UND wiederhergestellt werden kann
  • Wenn der Fehler nicht behoben wird, sollte das Programm beendet werden.

Wenn der Fehler behoben werden kann (der Benutzer hat "apple" anstelle einer Zahl eingegeben), beheben Sie ihn (fordern Sie die Eingabe erneut an, ändern Sie den Standardwert usw.).

Wenn der Fehler nicht lokal behoben werden kann, die Anwendung jedoch fortgesetzt werden kann (der Benutzer hat versucht, eine Datei zu öffnen, die Datei jedoch nicht vorhanden ist), ist ein Fehlercode angemessen.

Wenn der Fehler nicht lokal behoben werden kann und die Anwendung nicht ohne Wiederherstellung fortgesetzt werden kann (Sie haben nicht genügend Speicher / Speicherplatz / usw.), Ist eine Ausnahme der richtige Weg.


1
-1. Bitte lesen Sie die Frage sorgfältig durch. Die meisten Programmierer denken entweder, dass Ausnahmen für bestimmte Arten der Fehlerbehandlung geeignet sind oder dass sie niemals angemessen sind - sie erwägen nicht einmal die Möglichkeit, sie für andere, exotischere Formen der Flusskontrolle zu verwenden. Die Frage ist: Warum ist das so?
j_random_hacker

Sie sollten es auch sorgfältig lesen. Ich antwortete: "Was ist die Philosophie dahinter, außergewöhnlich konservativ mit ihrer Verwendung umzugehen?" mit meiner Philosophie dahinter, konservativ zu sein, wie sie verwendet werden.
Bill

IMHO haben Sie nicht erklärt, warum der Konservatismus notwendig ist. Warum sind sie manchmal nur "angemessen"? Warum nicht die ganze Zeit? (Übrigens denke ich, dass Ihr vorgeschlagener Ansatz in Ordnung ist, es ist mehr oder weniger das, was ich selbst mache, ich glaube nur nicht, dass es viel von dem Warum der Frage bekommt .)
j_random_hacker

Das OP stellte sieben verschiedene Fragen. Ich habe mich entschieden, nur eine zu beantworten. Es tut mir leid, dass Sie der Meinung sind, dass dies eine Abwertung wert ist.
Bill

0

Wer hat gesagt, dass sie konservativ verwendet werden sollten? Verwenden Sie niemals Ausnahmen für die Flusskontrolle und das wars. Und wer kümmert sich um die Kosten der Ausnahme, wenn sie bereits geworfen wird?


0

Meine zwei Cent:

Ich verwende gerne Ausnahmen, weil ich so programmieren kann, als ob keine Fehler auftreten würden. Mein Code bleibt also lesbar und nicht mit allen Arten der Fehlerbehandlung verstreut. Natürlich wird die Fehlerbehandlung (Ausnahmebehandlung) an das Ende (den Catch-Block) verschoben oder als Verantwortlichkeit der aufrufenden Ebene betrachtet.

Ein gutes Beispiel für mich ist entweder die Dateiverwaltung oder die Datenbankverarbeitung. Angenommen, alles ist in Ordnung, und schließen Sie Ihre Datei am Ende oder wenn eine Ausnahme auftritt. Oder setzen Sie Ihre Transaktion zurück, wenn eine Ausnahme aufgetreten ist.

Das Problem mit Ausnahmen ist, dass es schnell sehr ausführlich wird. Es sollte zwar ermöglichen, dass Ihr Code gut lesbar bleibt und sich nur auf den normalen Ablauf der Dinge konzentriert, aber wenn er konsistent verwendet wird, muss fast jeder Funktionsaufruf in einen Try / Catch-Block eingeschlossen werden, und er beginnt, den Zweck zu vereiteln.

Für ein ParseInt, wie bereits erwähnt, mag ich die Idee von Ausnahmen. Geben Sie einfach den Wert zurück. Wenn der Parameter nicht analysierbar war, lösen Sie eine Ausnahme aus. Es macht Ihren Code einerseits sauberer. Auf der Anruferebene müssen Sie so etwas tun

try 
{
   b = ParseInt(some_read_string);
} 
catch (ParseIntException &e)
{
   // use some default value instead
   b = 0;
}

Der Code ist sauber. Wenn ich ParseInt wie dieses überall verstreut bekomme, mache ich Wrapper-Funktionen, die die Ausnahmen behandeln und mir Standardwerte zurückgeben. Z.B

int ParseIntWithDefault(String stringToConvert, int default_value=0)
{
   int result = default_value;
   try
   {
     result = ParseInt(stringToConvert);
   }
   catch (ParseIntException &e) {}

   return result;
}

Fazit: Was ich während der Diskussion verpasst habe, war die Tatsache, dass ich mit Ausnahmen meinen Code einfacher / lesbarer machen kann, weil ich die Fehlerbedingungen besser ignorieren kann. Probleme:

  • Die Ausnahmen müssen noch irgendwo behandelt werden. Zusätzliches Problem: c ++ verfügt nicht über die Syntax, mit der angegeben werden kann, welche Ausnahmen eine Funktion auslösen kann (wie dies bei Java der Fall ist). Die aufrufende Ebene weiß also nicht, welche Ausnahmen möglicherweise behandelt werden müssen.
  • Manchmal kann Code sehr ausführlich werden, wenn jede Funktion in einen Try / Catch-Block eingeschlossen werden muss. Aber manchmal macht das immer noch Sinn.

Das macht es manchmal schwierig, ein gutes Gleichgewicht zu finden.


-1

Es tut mir leid, aber die Antwort lautet: "Sie werden aus einem bestimmten Grund Ausnahmen genannt." Diese Erklärung ist eine "Faustregel". Sie können keine vollständigen Umstände angeben, unter denen Ausnahmen verwendet werden sollten oder nicht, da eine schwerwiegende Ausnahme (englische Definition) für eine Problemdomäne ein normales Betriebsverfahren für eine andere Problemdomäne ist. Faustregeln sind nicht dazu gedacht, blind befolgt zu werden. Stattdessen sollen sie Ihre Untersuchung einer Lösung leiten. "Sie werden aus einem bestimmten Grund als Ausnahmen bezeichnet" besagt, dass Sie im Voraus bestimmen sollten, welchen normalen Fehler der Anrufer behandeln kann und welche ungewöhnlichen Umstände der Anrufer ohne spezielle Codierung (Catch-Blöcke) nicht behandeln kann.

Nahezu jede Programmierregel ist eine Richtlinie, die besagt: "Tun Sie dies nur, wenn Sie einen wirklich guten Grund haben": "Verwenden Sie niemals goto", "Vermeiden Sie globale Variablen", "Reguläre Ausdrücke erhöhen Ihre Anzahl von Problemen um eins "usw. Ausnahmen sind keine Ausnahme ....


... und der Fragesteller möchte wissen, warum es eine Faustregel ist, anstatt (noch einmal) zu hören, dass es eine Faustregel ist. -1.
j_random_hacker

Das habe ich in meiner Antwort bestätigt. Es gibt kein explizites Warum. Faustregeln sind per Definition vage. Wenn es ein explizites Warum gäbe, wäre dies eine Regel und keine Faustregel. Jede Erklärung in jeder anderen Antwort oben enthält Vorbehalte, daher erklären sie auch nicht, warum.
jmucchiello

Es mag kein definitives "Warum" geben, aber es gibt teilweise "Warum", die andere erwähnen, z. B. "Weil alle anderen das tun" (IMHO ein trauriger, aber wirklicher Grund) oder "Leistung" (IMHO ist dieser Grund normalerweise überbewertet , aber es ist trotzdem ein Grund). Das Gleiche gilt für die anderen Faustregeln wie das Vermeiden von goto (normalerweise erschwert dies die Analyse des Kontrollflusses mehr als eine äquivalente Schleife) und das Vermeiden globaler Variablen (sie führen möglicherweise zu einer starken Kopplung, was spätere Codeänderungen schwierig macht und normalerweise dasselbe Ziele können mit weniger Kopplung auf andere Weise erreicht werden).
j_random_hacker

Und all diese Gründe haben lange Vorbehaltslisten, was meine Antwort war. Es gibt kein wirkliches Warum, das über die breite Erfahrung in der Programmierung hinausgeht. Es gibt einige Faustregeln, die über das Warum hinausgehen. Dieselbe Faustregel kann dazu führen, dass "Experten" sich nicht darüber einig sind, warum. Sie selbst nehmen "Leistung" mit einem Körnchen Salz. Das wäre meine Topliste. Die Analyse der Flusskontrolle registriert sich nicht einmal bei mir, weil ich (wie "no goto") Probleme mit der Flusskontrolle überbewertet finde. Ich werde auch darauf hinweisen, dass meine Antwort versucht zu erklären, wie Sie die "banale" Antwort verwenden.
jmucchiello

Ich stimme Ihnen zu, soweit alle Faustregeln lange Vorbehaltslisten enthalten. Ich bin nicht der Meinung, dass es sich lohnt, die ursprünglichen Gründe für die Regel sowie die spezifischen Vorbehalte zu ermitteln. (Ich meine in einer idealen Welt, in der wir unendlich viel Zeit haben, über diese Dinge nachzudenken, und natürlich keine Fristen;)) Ich denke, das hat das OP gefordert.
j_random_hacker
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.