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).
Antworten:
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.
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.
quelle
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.
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.
quelle
Kernel-Aufrufe (oder andere System-API-Aufrufe) zur Verwaltung von Kernel (System) -Signalschnittstellen
Viele der Probleme der
goto
Aussage 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.)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ß?
Es ist schwieriger, Code zu warten, der die Verarbeitung von Ausnahmen umgeht.
quelle
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 .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.
quelle
catch
, obwohl Sie sich in diesem Fall fast genauso gutstd::terminate()
selbst nennen könnten . Insgesamt scheint mir dieses Argument zu sagen, dass "niemals Ausnahmen verwenden" und nicht "nur selten Ausnahmen verwenden".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.
quelle
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.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.
quelle
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:
Andererseits kann die Verwendung von Ausnahmen in neuen Projekten mit einem starken Team den Code sauberer, einfacher zu warten und noch schneller machen:
quelle
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:
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:
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.
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
/catch
Block: 25,455 SekundenVerwendung
int.TryParse
: 1,637 MillisekundenFü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
/catch
Block: 29,989 SekundenVerwendung
File.Exists
: 22,820 MillisekundenViele 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.
quelle
GetNine
macht 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.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.
quelle
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
catch
Block 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 habenf
, die häufig Ausnahmen auslöst, müssen wir jetzt darauf achten, unseretry
/catch
-Blöcke bei jedem Aufruf von zu verwendenf
. Das ist eine ziemlich schlechte Kopplung zwischen Schnittstelle und Implementierung.quelle
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.
quelle
Mein Ansatz zur Fehlerbehandlung ist, dass es drei grundlegende Arten von Fehlern gibt:
assert
Fall 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.
quelle
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:
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.
quelle
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
ParseInt
mit 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
ParseInt
auftritt, 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.
quelle
parseInt
tatsä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.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.
quelle
finally
Blöcke unterscheiden sich hinsichtlich des Implementierungsaufwands nicht von C ++ - Destruktoren.finally
die 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.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"
quelle
incomeType
.quelle
at()
? Das heißt, jedernew
kann werfen, also ...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.
quelle
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.
quelle
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.
quelle
Berechtigter Fall, um eine Ausnahme auszulösen:
Unzulässiger Fall:
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.
quelle
try
Blocks ansammeln , und dann sind Sie sich nicht mehr sicher, ob dercatch
Block 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.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.
quelle
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!
quelle
std::terminate()
, immer noch zerstören.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.
quelle
Ich benutze Ausnahmen, wenn:
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.
quelle
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?
quelle
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:
Das macht es manchmal schwierig, ein gutes Gleichgewicht zu finden.
quelle
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 ....
quelle