Frage Warum sollten Ausnahmen konservativ verwendet werden?


Ich sehe / höre oft Leute sagen, dass Ausnahmen nur selten verwendet werden sollten, aber erkläre nie warum. Obwohl das wahr sein mag, ist die Begründung normalerweise ein Glib: "Es wird aus gutem Grund eine Ausnahme genannt" was für mich die Art von Erklärung zu sein scheint, die niemals von einem respektablen Programmierer / Ingenieur akzeptiert werden sollte.

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

Ich habe schon einige Performance-Analysen gesehen, aber auf einer Ebene, die für einige Systeme relevant und für andere irrelevant ist.

Wiederum stimme ich nicht unbedingt überein, dass sie für besondere Umstände gerettet werden sollten, aber ich frage mich, was der Konsensgrundsatz ist (wenn so etwas existiert).


75
2017-11-16 18:46


Ursprung


Antworten:


Der primäre Reibungspunkt ist Semantik. Viele Entwickler missbrauchen Ausnahmen und werfen sie bei jeder Gelegenheit. Die Idee ist, Ausnahme für etwas außergewöhnliche Situation zu verwenden. Zum Beispiel zählt eine falsche Benutzereingabe nicht als Ausnahme, da Sie dies erwarten und dafür bereit sind. Wenn Sie jedoch versucht haben, eine Datei zu erstellen und nicht genügend Speicherplatz auf der Festplatte vorhanden ist, dann ist dies eine eindeutige Ausnahme.

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

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

Selten, weil Sie nicht erwarten, dass Ihr Programm bei jedem Tastendruck oder jeder missbräuchlichen Benutzereingabe abstürzt. Angenommen, die Datenbank ist plötzlich nicht mehr erreichbar, möglicherweise ist nicht genügend Speicherplatz auf der Festplatte vorhanden, einige Dienste von Drittanbietern, auf die Sie angewiesen sind, sind offline, dies alles kann passieren, aber ziemlich selten wären dies klare Ausnahmefälle.

Teuer, weil das Auslösen einer Ausnahme den normalen Programmablauf unterbricht. Die Laufzeitumgebung löscht den Stapel, bis ein geeigneter Ausnahmebehandler gefunden wird, der die Ausnahme verarbeiten kann. Es wird außerdem die Anrufinformationen auf dem gesamten Weg sammeln, um an das Ausnahmeobjekt übergeben zu werden, das der Handler empfangen wird. Es hat alles Kosten.

Dies soll nicht heißen, dass es keine Ausnahmen für die Verwendung von Ausnahmen (smile) geben kann. Manchmal kann es die Codestruktur vereinfachen, wenn Sie eine Ausnahme auslösen, anstatt die Rückkehrcodes über viele Ebenen weiterzuleiten. Als eine einfache Regel, wenn Sie erwarten, dass eine Methode oft aufgerufen wird und eine "außergewöhnliche" Situation die Hälfte der Zeit entdeckt, dann ist es besser, eine andere Lösung zu finden. Wenn Sie jedoch die meiste Zeit normalen Betriebsablauf erwarten, während diese "außergewöhnliche" Situation nur unter seltenen Umständen auftreten kann, dann ist es in Ordnung, eine Ausnahme auszulösen.

@ Comments: Exception kann definitiv in einigen 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 es in der Praxis ziemlich selten ist.

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 möglicherweise Objekte in einem inkonsistenten Zustand und einige offene Ressourcen unbillig bleiben. Sicher, C # hat die using-Anweisung, die dafür sorgt, dass das Objekt auch dann entsorgt wird, wenn eine Ausnahme aus dem using body geworfen wird. Aber lassen Sie uns für den Moment von der Sprache abstrahieren. Angenommen, das Framework wird keine Objekte für Sie bereitstellen. Sie tun es manuell. Sie haben ein System, um Ressourcen und Speicher anzufordern und freizugeben. Sie haben eine systemweite Vereinbarung, wer für die Freigabe von Objekten und Ressourcen in welchen Situationen zuständig ist. Sie haben Regeln, wie Sie mit externen Bibliotheken umgehen. Es funktioniert gut, wenn das Programm dem normalen Betriebsablauf folgt. Aber plötzlich in der Mitte der Ausführung werfen Sie eine Ausnahme. Die Hälfte der Ressourcen bleibt unbebaut. Die Hälfte wurde noch nicht angefordert. Wenn die Operation jetzt transaktional sein sollte, ist sie defekt. Ihre Regeln für die Verarbeitung von Ressourcen funktionieren nicht, da die für die Ressourcenfreigabe verantwortlichen Codeteile nicht ausgeführt werden. Wenn jemand diese Ressourcen nutzen möchte, könnte er sie in einem inkonsistenten Zustand finden und ebenfalls abstürzen, weil sie diese besondere Situation nicht vorhersagen konnten.

Nehmen wir an, Sie wollten eine Methode M () Call Methode N (), um etwas Arbeit zu tun und arrangieren für einige Ressourcen dann zurück zu M (), die es verwenden und dann entsorgen. Fein. Nun läuft etwas in N () schief und es löst eine Ausnahme aus, die Sie in M ​​() nicht erwartet haben, so dass die Ausnahme nach oben springt, bis sie in einer Methode C () gefangen wird, die keine Ahnung hat, was tief unten passiert in N () und ob und wie man einige Ressourcen freigibt.

Mit den Ausnahmen zum Auslösen erstellen Sie eine Möglichkeit, Ihr Programm in viele neue unvorhersehbare Zwischenzustände zu bringen, die schwer zu prognostizieren, zu verstehen und zu bewältigen sind. Es ist etwas ähnlich wie GOTO. Es ist sehr schwierig, ein Programm zu entwerfen, das seine Ausführung von einem Ort zum anderen springen kann. Es wird auch schwierig sein, es zu warten und zu debuggen. Wenn das Programm an Komplexität zunimmt, verlieren Sie nur einen Überblick darüber, was wann und wo passiert, um es zu beheben.


88
2017-11-16 18:49



Während "Exceptions in Ausnahmefällen auslösen" die beste Antwort ist, können Sie tatsächlich definieren, was diese Umstände sind: wenn Vorbedingungen erfüllt sind, aber Nachbedingungen nicht erfüllt werden können. Dadurch können Sie strengere, engere und nützlichere Nachbedingungen schreiben, ohne die Fehlerbehandlung zu opfern. Andernfalls müssen Sie ohne Ausnahme die Nachbedingung so ändern, dass alle möglichen Fehlerzustände berücksichtigt werden.

  • Voraussetzungen muss wahr sein Vor eine Funktion aufrufen.
  • Nachbedingungist was die Funktion garantiert nach es kehrt zurück.
  • Ausnahmesicherheit gibt an, wie sich Ausnahmen auf die interne Konsistenz einer Funktion oder Datenstruktur auswirken, und sich häufig mit von außen übernommenem Verhalten befassen (z. B. funktor, ctor eines Vorlagenparameters usw.).

Konstruktoren

Es gibt sehr wenig, was man über jeden Konstruktor für jede Klasse sagen kann, die möglicherweise in C ++ geschrieben werden könnte, aber es gibt ein paar Dinge. Das wichtigste unter ihnen ist, dass konstruierte Objekte (d. H. Für die der Konstruktor erfolgreich war) zerstört werden. Sie können diese Nachbedingung nicht ändern, da die Sprache annimmt, dass sie wahr ist und Destruktoren automatisch aufruft.  (Technisch können Sie die Möglichkeit eines undefinierten Verhaltens akzeptieren, für das die Sprache zuständig ist Nein garantiert über etwas, aber das ist wahrscheinlich anderswo besser abgedeckt.)

Die einzige Alternative zum Auslösen einer Ausnahme, wenn ein Konstruktor nicht erfolgreich sein kann, besteht darin, die Basisdefinition der Klasse (die "Klasseninvariante") so zu ändern, dass gültige "Null" - oder Zombie-Zustände möglich sind und der Konstrukteur dadurch "erfolgreich" ist, indem er einen Zombie konstruiert .

Zombie-Beispiel

Ein Beispiel für diese Zombie-Modifikation ist std :: ifstream, und Sie müssen immer seinen Zustand überprüfen, bevor Sie ihn verwenden können. weil Std :: ZeichenfolgeZum Beispiel ist es nicht immer gewährleistet, dass Sie es sofort nach dem Bau verwenden können. Stellen Sie sich vor, Sie müssten Code wie dieses Beispiel schreiben und wenn Sie vergessen hätten, nach dem Zombie-Status zu suchen, würden Sie entweder unrichtige 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 Invariante und die Schnittstelle der Klasse für eine Situation ändern müssen Zeichenfolge kann sich weder vorhersagen noch handhaben.

Eingabebeispiel validieren

Lassen Sie uns ein allgemeines Beispiel ansprechen: Validierung von Benutzereingaben. Nur weil wir gescheiterte Eingaben zulassen wollen, heißt das nicht Parsing-Funktion muss das in seiner Nachbedingung enthalten. Es 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 in C ++ Scoping benötigen, um RAII / SBRM zu brechen. Ein Beispiel in Python, das dieses Problem nicht hat und etwas zeigt, das C ++ hätte - 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

Vorbedingungen müssen nicht unbedingt überprüft werden - ein Verstoß weist immer auf einen logischen Fehler hin, und sie sind die Verantwortung des Anrufers. Wenn Sie diese jedoch überprüfen, ist das Auslösen einer Ausnahme angemessen. (In einigen Fällen ist es sinnvoller, den Müll zurückzuschicken oder das Programm zum Absturz zu bringen, obwohl diese Aktionen in anderen Kontexten schrecklich falsch sein können. Wie man am besten damit umgeht undefiniertes Verhalten ist ein anderes Thema.)

Insbesondere kontrastieren die std :: logic_error und std :: runtime_error Zweige der stdlib-Ausnahmehierarchie. Ersteres wird häufig für Vorbedingungsverletzungen verwendet, während Letzteres eher für Nachbedingungen geeignet ist.


60
2017-11-16 19:06



  1. Teuer 
     Kernelaufrufe (oder andere System-API-Aufrufe), um Kernel (System) -Signalschnittstellen zu verwalten
  2. Schwer zu analysieren
     Viele der Probleme der goto Anweisung gilt für Ausnahmen. Sie springen oft in mehreren Routinen und Quelldateien über potenziell große Mengen an Code. Dies ist beim Lesen des Zwischenquellcodes nicht immer offensichtlich. (Es ist in Java.)
  3. Nicht immer von Zwischencode erwartet
     Der Code, der übersprungen wird, wurde möglicherweise mit der Möglichkeit eines Ausnahmeausstiegs geschrieben oder nicht. Wenn es ursprünglich so geschrieben wurde, wurde es möglicherweise nicht in diesem Sinne beibehalten. Denken Sie: Speicherlecks, Datei-Deskriptor-Lecks, Socket-Lecks, wer weiß?
  4. Wartungskomplikationen
    Es ist schwieriger, Code zu pflegen, der die Verarbeitung von Ausnahmen umgeht.

39
2017-11-17 03:18



Das Auslösen einer Ausnahme ähnelt in gewisser Weise einer goto-Anweisung. Tun Sie das für die Flusskontrolle, und Sie enden mit unverständlichem Spaghetti-Code. Schlimmer noch, in einigen Fällen wissen Sie nicht einmal, wohin genau der Sprung geht (d. H. Wenn Sie die Ausnahme im gegebenen Kontext nicht abfangen). Dies verletzt eklatant das Prinzip der "geringsten Überraschung", das die Wartbarkeit verbessert.


22
2017-11-16 18:52



Ausnahmen machen es schwieriger, über den Zustand Ihres Programms nachzudenken. In C ++ zum Beispiel müssen Sie extra denken, um sicherzustellen, dass Ihre Funktionen äußerst sicher sind, als wenn Sie nicht müssten.

Der Grund dafür ist, dass ein Funktionsaufruf ohne Ausnahmen entweder zurückkehren oder das Programm zuerst beenden kann. Mit Ausnahmen kann ein Funktionsaufruf entweder zurückkehren oder er kann das Programm beenden oder er kann irgendwo zu einem Catch-Block springen. Sie können also nicht mehr dem Kontrollfluss folgen, indem Sie nur den Code vor sich sehen. Sie müssen wissen, ob die aufgerufenen Funktionen auslösen können. Sie müssen möglicherweise wissen, was geworfen werden kann und wo es gefangen wird, abhängig davon, ob Sie sich interessieren, wo die Kontrolle geht, oder nur darauf achten, dass es den aktuellen Umfang verlässt.

Aus diesem Grund sagen die Leute "nimm keine Ausnahmen, außer wenn die Situation wirklich außergewöhnlich ist". Wenn Sie sich darauf beschränken, bedeutet "wirklich außergewöhnlich", dass "einige Situationen eingetreten sind, in denen die Vorteile der Behandlung mit einem Fehlerrückgabewert durch die Kosten aufgewogen werden". Also ja, das ist so etwas wie eine leere Aussage, obwohl, sobald Sie ein paar Instinkte für "wirklich außergewöhnlich" haben, wird es eine gute Faustregel. Wenn Leute über Flusskontrolle sprechen, meinen sie, dass die Fähigkeit, lokal zu argumentieren (ohne Bezug auf Catch-Blöcke), ein Vorteil von Rückgabewerten ist.

Java hat eine breitere Definition von "wirklich außergewöhnlich" als C ++. C ++ - Programmierer wollen eher den Rückgabewert einer Funktion als Java-Programmierer betrachten, also könnte in Java "wirklich außergewöhnlich" bedeuten: "Ich kann kein Nicht-Null-Objekt als Ergebnis dieser Funktion zurückgeben". In C ++ bedeutet es eher, dass "ich sehr bezweifle, 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 Wert zurückgibt, der auf einen Fehler hinweist. In allen Fällen kommt es jedoch darauf an, welchen Code Sie Ihrem Anrufer zum Schreiben zwingen möchten. Es ist also eine Frage des Codierungsstils: Sie müssen einen Konsens darüber erzielen, wie Ihr Code aussehen soll und wie viel "Fehlerüberprüfungs" -Code Sie schreiben möchten, wie viele "Ausnahmesicherheits" -Regeln Sie ausführen möchten.

Der breite Konsens in allen Sprachen scheint zu sein, dass dies am besten in Bezug darauf getan wird, wie wahrscheinlich der Fehler sein wird (da nicht behebbare Fehler zu keinem Code mit Ausnahmen führen, aber trotzdem eine Überprüfung und Rückgabe erfordern). Fehler im Code, der Fehler zurückgibt). So erwarten die Leute, dass "diese Funktion, die ich anrufe, eine Ausnahme auslöst" bedeutet "ich kann nicht weitermachen ", nicht nur"es kann nicht fortgesetzt werden. "Das ist nicht in Ausnahmen enthalten, es ist nur eine Gewohnheit, aber wie jede gute Programmierpraxis, es ist ein Brauch von klugen Menschen, die es anders versucht haben und nicht die Ergebnisse genossen. Ich hatte auch schlecht Erfahrungen, die zu viele Ausnahmen werfen, also persönlich, denke ich in "wirklich außergewöhnlich", es sei denn, etwas an der Situation macht eine Ausnahme besonders attraktiv.

Übrigens, abgesehen von den Überlegungen über den Zustand Ihres Codes, gibt es auch Auswirkungen auf die Leistung. Ausnahmen sind in der Regel billig, in Sprachen, in denen Sie sich um die Leistung kümmern müssen. Sie können schneller sein als mehrere Ebenen von "oh, das Ergebnis ist ein Fehler, ich würde mich am besten mit einem Fehler auch dann verlassen". In den schlechten alten Zeiten gab es echte Ängste, dass das Werfen einer Ausnahme, das Fangen und das Fortfahren mit dem nächsten Ding das machen würde, was du tust, so langsam, dass es nutzlos ist. In diesem Fall bedeutet "wirklich außergewöhnlich", dass die Situation so schlecht ist, dass die schreckliche Leistung keine Rolle mehr spielt. Das ist nicht mehr der Fall (obwohl eine Ausnahme in einer engen Schleife noch spürbar ist) und zeigt hoffentlich, warum die Definition von "wirklich außergewöhnlich" flexibel sein muss.


16
2017-11-16 19:28



Es gibt wirklich keinen Konsens. Das ganze Problem ist etwas subjektiv, weil die "Angemessenheit" des Auslösens einer Ausnahme oft durch existierende Praktiken innerhalb der Standardbibliothek der Sprache selbst vorgeschlagen wird. Die C ++ - Standardbibliothek löst Ausnahmen viel seltener aus als etwa die Java-Standardbibliothek, die fast immer bevorzugt Ausnahmen, selbst für erwartete Fehler wie ungültige Benutzereingabe (z. Scanner.nextInt). Dies beeinflusst meines Erachtens die Meinung der Entwickler darüber, wann es angebracht ist, eine Ausnahme zu machen.

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


11
2017-11-16 18:56



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 von Programmierern, spezielle Techniken und das Fehlen von großem, nicht ausnahmesicherem Code. Wenn Sie eine riesige alte Codebase haben, ist sie fast immer nicht ausnahmesicher. Ich bin mir sicher, dass du es nicht umschreiben willst.

Wenn Sie Ausnahmen ausführlich verwenden möchten, dann:

  • Sei bereit, deine Leute darüber zu unterrichten, welche Ausnahme die Sicherheit ist
  • Sie sollten keine rohe Speicherverwaltung verwenden
  • Verwenden Sie RAII ausgiebig

Auf der anderen Seite kann die Verwendung von Ausnahmen in neuen Projekten mit einem starken Team Code sauberer, einfacher zu warten und noch schneller machen:

  • Sie werden Fehler nicht übersehen oder ignorieren
  • Sie müssen diese Überprüfungen der Rückkehrcodes nicht schreiben, ohne wirklich zu wissen, was mit falschem Code auf niedriger Ebene zu tun ist
  • Wenn Sie gezwungen sind, einen Ausnahmecode zu schreiben, wird er strukturierter

7
2017-11-16 20:36



EDIT 20.11.2009:

Ich habe gerade gelesen Dieser MSDN-Artikel zur Verbesserung der Leistung von verwaltetem Code 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 zum Behandeln 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.

Natürlich ist dies nur für .NET, und es richtet sich auch speziell an die Entwicklung von Hochleistungsanwendungen (wie ich); also ist es offensichtlich keine universelle Wahrheit. Dennoch gibt es viele .NET-Entwickler da draußen, also war es meiner Meinung nach erwähnenswert.

BEARBEITEN:

Zuallererst, lassen Sie uns eines klarstellen: Ich habe nicht die Absicht, mich wegen der Performance-Frage mit jemandem zu streiten. Im Allgemeinen neige ich dazu, denjenigen zuzustimmen, die glauben, dass vorzeitige Optimierung eine Sünde ist. Lassen Sie mich jedoch nur zwei Punkte erwähnen:

  1. Das Poster fragt nach einer objektiven Begründung für die konventionelle Weisheit, dass Ausnahmen sparsam eingesetzt werden sollten. Wir können Lesbarkeit und richtiges Design diskutieren, was wir wollen; aber das sind subjektive Angelegenheiten mit Leuten, die bereit sind, auf beiden Seiten zu argumentieren. Ich denke, das Plakat ist sich dessen bewusst. Tatsache ist, dass die Verwendung von Ausnahmen zur Steuerung des Programmablaufs oft eine ineffiziente Vorgehensweise ist. Nein nicht immer, aber häufig. Deshalb ist es vernünftig, die 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 logische Folge davon ist, dass es einen Unterschied zwischen dem Schreiben von etwas gibt, das robust ist, wenn nicht optimiert, und etwas, das einfach nur ineffizient ist. Manchmal denke ich, wenn Leute über Dinge wie Ausnahmebehandlung diskutieren, reden sie wirklich nur aneinander vorbei, weil sie grundlegend verschiedene Dinge diskutieren.

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

Beispiel 1: Erkennung ungültiger Benutzereingaben

Dies ist ein Beispiel dafür, was ich als Ausnahme bezeichnen 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. Niemand streitet damit. Aber es sollte sei etwas wie:

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

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

Beispiel 2: Überprüfen auf das Vorhandensein einer Datei

Ich denke, dieses Szenario ist tatsächlich sehr verbreitet. Sicherlich scheint viel "akzeptabler" für viele Leute, da es sich um Datei-I / O 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 zu sein, oder? Wir versuchen, eine Datei zu öffnen. Wenn es nicht da ist, fangen wir diese Ausnahme und versuchen, eine andere Datei zu öffnen ... Was ist daran falsch?

Nichts wirklich. Aber bedenken Sie diese Alternative, die nicht 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();
    }
}

Natürlich, wenn die Leistung dieser beiden Ansätze tatsächlich die gleiche wäre, wäre das wirklich nur ein doktrinäres Problem. Also, schauen wir mal. Für das erste Codebeispiel habe ich eine Liste von 10000 zufälligen Zeichenfolgen erstellt, von denen keine eine richtige Ganzzahl darstellt, und dann am Ende eine gültige ganzzahlige Zeichenfolge hinzugefügt. Unter Verwendung der beiden oben genannten Ansätze waren dies meine Ergebnisse:

Verwenden try/catch Block: 25.455 Sekunden
Verwenden int.TryParse: 1,637 Millisekunden

Für das zweite Beispiel habe ich im Grunde genommen das Gleiche gemacht: Ich habe eine Liste von 10000 zufälligen Strings erstellt, von denen keiner ein gültiger Pfad war, und dann einen gültigen Pfad bis zum Ende hinzugefügt. Dies waren die Ergebnisse:

Verwenden try/catch Block: 29.989 Sekunden
Verwenden File.Exists: 22.820 Millisekunden

Viele Leute würden darauf antworten, indem sie sagen: "Ja, nun, es ist extrem unrealistisch, 10.000 Ausnahmen zu werfen und einzufangen; das übertreibt die Ergebnisse." Natürlich tut es das. Der Unterschied zwischen dem Auslösen einer Ausnahme und der alleinigen Handhabung schlechter Eingaben wird für den Benutzer nicht spürbar sein. Tatsache ist, dass die Verwendung von Ausnahmen in diesen beiden Fällen von 1.000 bis über 10.000 mal langsamer als die alternativen Ansätze, die genauso lesbar sind - wenn nicht noch mehr.

Deshalb habe ich das Beispiel der GetNine() Methode unten. Es ist nicht so unerträglich langsam oder inakzeptabel langsam; es ist, dass es langsamer ist als es sollte Sein... aus keinem guten Grund.

Auch dies sind nur zwei Beispiele. Von Kurs Es wird Zeiten geben, in denen der Leistungseinbruch bei der Verwendung von Ausnahmen nicht so schwerwiegend ist (Pavel hat recht; schließlich hängt dies von der Implementierung ab). Alles was ich sage ist: Lasst uns den Tatsachen ins Auge sehen, Leute - in Fällen wie dem obigen ist das Werfen und Fangen einer Ausnahme analog zu GetNine(); es ist nur eine ineffiziente Art, etwas zu tun das könnte leicht besser gemacht werden.


Sie fragen nach einer Begründung, als wäre dies eine der Situationen, in der jeder auf einen fahrenden Zug aufgesprungen ist, ohne zu wissen warum. Aber in der Tat ist die Antwort offensichtlich, und ich denke, du weißt es bereits. Ausnahmebehandlung hat schreckliche Leistung.

OK, vielleicht ist es in Ordnung für Ihr besonderes Geschäftsszenario, aber relativ gesehendas Werfen / Fangen einer Ausnahme führt viel mehr Overhead ein, als in vielen, vielen Fällen notwendig ist. Du weißt es, ich weiß es: meistensWenn Sie Ausnahmen zum Steuern des Programmablaufs verwenden, schreiben Sie nur langsamen Code.

Sie können auch fragen: Warum ist dieser Code schlecht?

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

Ich würde wetten, dass Sie, wenn Sie diese Funktion profilieren würden, feststellen würden, dass sie für Ihre typische Geschäftsanwendung ziemlich schnell arbeitet. Das ändert nichts an der Tatsache, dass es eine schrecklich ineffiziente Art ist, etwas zu erreichen, das viel besser gemacht werden könnte.

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


7
2017-11-16 19:57



Alle Faustregeln über Ausnahmen gehen auf subjektive Begriffe zurück. Sie sollten nicht erwarten, harte und schnelle Definitionen zu bekommen, wann Sie sie verwenden und wann nicht. "Nur in Ausnahmefällen". Schöne zirkuläre Definition: Ausnahmen sind für außergewöhnliche Umstände.

Die Verwendung von Ausnahmen fällt in den gleichen Bucket wie "Woher weiß ich, ob dieser Code eine oder zwei Klassen umfasst?" Es ist teilweise ein stilistisches Thema, teilweise eine Vorliebe. Ausnahmen sind ein Werkzeug. Sie können benutzt und missbraucht werden, und die Grenze zwischen beiden zu finden, ist Teil der Kunst und der Fähigkeit des Programmierens.

Es gibt viele Meinungen und Kompromisse. Finde etwas, das zu dir spricht, und folge ihm.


6
2017-11-16 19:15