it-swarm.com.de

Warum werden Ausnahmen als besser angesehen als explizite Fehlertests?

Mögliches Duplikat:
Defensive Programmierung vs. Ausnahmebehandlung?
if/else-Anweisungen oder Ausnahmen

Ich stoße oft auf hitzige Blog-Posts, in denen der Autor das Argument "Ausnahmen gegen explizite Fehlerprüfung" verwendet, um seine bevorzugte Sprache gegenüber einer anderen Sprache zu vertreten. Der allgemeine Konsens scheint zu sein, dass Sprachen, die Ausnahmen verwenden, von Natur aus besser/sauberer sind als Sprachen, die stark auf Fehlerprüfungen durch explizite Funktionsaufrufe angewiesen sind.

Wird die Verwendung von Ausnahmen als bessere Programmierpraxis angesehen als die explizite Fehlerprüfung, und wenn ja, warum?

48
Richard Keller

In meinen Augen ist das größte Argument der Unterschied, was passiert, wenn der Programmierer einen Fehler macht. Das Vergessen, einen Fehler zu behandeln, ist ein sehr häufiger und leichter Fehler.

Wenn Sie Fehlercodes zurückgeben, können Sie einen Fehler unbemerkt ignorieren. Wenn malloc beispielsweise fehlschlägt, gibt es NULL zurück und legt das globale errno fest. Also sollte korrekter Code reichen

void* myptr = malloc(1024);
if (myptr == NULL) {
    perror("malloc");
    exit(1);
}
doSomethingWith(myptr);

Aber es ist sehr einfach und zweckmäßig, stattdessen nur zu schreiben:

void* myptr = malloc(1024);
doSomethingWith(myptr);

dies wird unerwartet NULL an Ihre andere Prozedur übergeben und wahrscheinlich das sorgfältig festgelegte errno verwerfen. An dem Code ist nichts sichtbar Falsches, was darauf hinweist, dass dies möglich ist.

In einer Sprache, die Ausnahmen verwendet, würden Sie stattdessen schreiben

MyCoolObject obj = new MyCoolObject();
doSomethingWith(obj);

In diesem (Java-) Beispiel gibt der Operator new entweder ein gültiges initialisiertes Objekt zurück oder löst OutOfMemoryError aus. Wenn ein Programmierer damit umgehen muss, kann er es fangen. In dem üblichen (und zweckmäßigerweise auch faulen) Fall, in dem es sich um einen schwerwiegenden Fehler handelt, beendet die Ausnahmeverbreitung das Programm auf relativ saubere und explizite Weise.

Dies ist einer der Gründe, warum Ausnahmen bei ordnungsgemäßer Verwendung das Schreiben von klarem und sicherem Code erheblich erleichtern können. Dieses Muster gilt für viele, viele Dinge, die schief gehen können, und nicht nur für die Zuweisung von Speicher.

63

Während Stevens Antwort eine gute Erklärung liefert, gibt es einen anderen Punkt, den ich ziemlich wichtig finde. Wenn Sie einen Fehlercode überprüfen, können Sie den Fehlerfall manchmal nicht sofort behandeln. Sie müssen den Fehler explizit über den Aufrufstapel weitergeben. Wenn Sie eine große Funktion umgestalten, müssen Sie möglicherweise den gesamten Code für die Fehlerprüfung Ihrer Unterfunktion hinzufügen.

Mit Ausnahmen müssen Sie sich nur um Ihren Hauptfluss kümmern. Wenn ein Code InvalidOperationError auslöst, können Sie diesen Code dennoch in eine Unterfunktion verschieben, und die Fehlerverwaltungslogik wird beibehalten.

Ausnahmen ermöglichen es Ihnen also, schneller umzugestalten und Kesselplatten zu vermeiden.

57
Simon Bergot

Aus einem anderen Blickwinkel: Bei der Fehlerbehandlung dreht sich alles um Sicherheit. Ein ungeprüfter Fehler bricht alle Annahmen und Voraussetzungen, auf denen der folgende Code basiert. Dies kann viele externe Angriffsmethoden eröffnen: Von einem einfachen DoS über nicht autorisierten Datenzugriff bis hin zu Datenkorruption und vollständiger Systeminfiltration.

Sicher, es hängt von der spezifischen Anwendung ab, aber wenn Annahmen und Voraussetzungen gebrochen sind, sind alle Wetten ungültig. In einer komplexen Software kann man einfach nicht mehr mit Sicherheit sagen, was von da an möglich ist und was von außen verwendet werden kann und was nicht.

Angesichts dessen gibt es eine grundlegende Beobachtung: Wenn Sicherheit als optionales Add-On behandelt wird, das später angehängt werden kann, schlägt dies am häufigsten fehl. Es funktioniert am besten, wenn es bereits in den ersten grundlegenden Entwurfsphasen berücksichtigt und von Anfang an eingebaut wurde.

Dies ist, was Sie im Grunde mit Ausnahmen erhalten: Eine bereits integrierte Fehlerbehandlungsinfrastruktur, die aktiv ist, auch wenn Sie sich nicht darum kümmern. Bei expliziten Tests müssen Sie es selbst erstellen. Sie müssen es als ersten Schritt bauen. Der Beginn des Schreibens von Funktionen, die Fehlercodes zurückgeben, ohne über das Gesamtbild nachzudenken, bis Sie sich in der letzten Phase Ihrer Anwendungsentwicklung befinden, ist praktisch eine zusätzliche Fehlerbehandlung, die zum Scheitern verurteilt ist.

Nicht dass ein eingebautes System in irgendeiner Weise hilft. Die meisten Programmierer wissen nicht, wie man Fehler behandelt. Meistens ist es einfach zu komplex.

  • Es können so viele Fehler auftreten, und jeder Fehler erfordert seine eigene Behandlung und seine eigenen Aktionen und Reaktionen.

  • Selbst der gleiche Fehler kann je nach Kontext unterschiedliche Aktionen erfordern. Was ist mit einer nicht gefundenen Datei oder einem nicht genügend Speicher?

    • FNF - Eine Drittanbieter-Bibliothek, die für die Ausführung Ihrer App benötigt wird? Beenden.
    • FNF - Die Startkonfigurationsdatei Ihrer Anwendung? Beginnen Sie mit den Standardeinstellungen.
    • FNF - Eine Datendatei in einer Netzwerkfreigabe, die der Benutzer öffnen soll? Verschwindende Dateien können jederzeit auftreten, selbst in den Mikrosekunden zwischen Exists () und Open (). Es ist nicht einmal eine "Ausnahme" im wörtlichen Sinne des Wortes.
    • OOM - Für eine 2 GB Zuweisung? Keine Überraschung.
    • OOM - Für eine 100-Byte-Zuordnung? Du bist in ernsthaften Schwierigkeiten.
  • Die Fehlerbehandlung durchläuft alle sorgfältig getrennten Abstraktionsschichten. Bei einem Fehler in der untersten Ebene muss der Benutzer möglicherweise mit einer GUI-Nachricht benachrichtigt werden. Möglicherweise muss der Benutzer entscheiden, was jetzt zu tun ist. Möglicherweise muss protokolliert werden. Möglicherweise sind Wiederherstellungsvorgänge in einem anderen Teil erforderlich, z. Öffnen Sie Datenbank- oder Netzwerkverbindungen. Usw.

  • Was ist mit Staat? Wenn Sie eine Methode für ein Objekt aufrufen, die Statusänderungen aufruft und einen Fehler auslöst:

    • Befindet sich das Objekt in einem inkonsistenten Zustand und muss neu erstellt werden?
    • Ist der Objektstatus konsistent, aber (teilweise) geändert und erfordert ein zusätzliches Rollback oder eine Neuerstellung?
    • Wird ein Rollback innerhalb des Objekts durchgeführt und bleibt es für den Aufrufer unverändert?
    • Wo ist es überhaupt sinnvoll, was zu tun?

Zeigen Sie mir einfach ein Lernbuch, in dem die Fehlerbehandlung von Anfang an streng gestaltet und folglich in allen Beispielen verwendet wird, ohne auf Kürze und Lesbarkeit und als Übung für den Leser zu verzichten. Ob dies von einem pädagogischen POV aus anwendbar ist, ist eine andere Frage, aber es ist keine Überraschung, dass die Fehlerbehandlung oft genug ein zweiter oder dritter Gedanke ist, wenn es der allererste sein sollte.

19
Secure

Ich würde Ausnahmen gerne als ...

Sie ermöglichen es Ihnen, Code so zu schreiben, wie er normalerweise geschrieben wird

Beim Schreiben von Code muss das menschliche Gehirn eine Reihe von Dingen unter einen Hut bringen:

  • Welche Logik versuche ich hier auszudrücken?
  • Wie schaffe ich das eigentlich in Sprache XXXX?
  • Wie passt dieser Code in den Rest der Anwendung?
  • Warum erhalte ich diese Buildfehler immer wieder?
  • Vergesse ich die Randbedingungen?
  • Ist dieser Code lesbar und wartbar genug?
  • Was passiert, wenn etwas in diesem Code fehlschlägt?
  • Produziere ich Code in konsistentem Stil und verwende die richtigen Codierungskonventionen?

Jede dieser Kugeln erfordert eine bestimmte Konzentration und Konzentration ist eine begrenzte Ressource. Aus diesem Grund vergessen Personen, die neu in einem Projekt sind, häufig Dinge am Ende der Liste. Sie sind keine schlechten Menschen, aber weil so viele Dinge für sie neu sein können (Sprache, Projekt, seltsame Fehler usw.), haben sie einfach nicht die Fähigkeit, mit anderen Kugeln umzugehen.

Ich habe meinen Teil der Codeüberprüfungen durchgeführt und dieser Trend ist sehr offensichtlich. Eine Person mit weniger Erfahrung wird mehr Dinge vergessen. Diese Dinge beinhalten Stil und oft genug Fehlerbehandlung. Wenn es Menschen schwer fällt, den Code zum Laufen zu bringen, haben sie die Fehlerbehandlung im Hinterkopf. Manchmal gehen sie zurück und fügen hier und da einige if-Anweisungen hinzu, aber seien Sie versichert, sie werden eine ganze Reihe von Dingen vergessen.

Mit der Ausnahmebehandlung können Sie Code schreiben, in dem das Fleisch Ihrer Logik so geschrieben ist, als ob keine Fehler vorliegen. Sie führen einen Funktionsaufruf durch und die nächste Zeile kann einfach davon ausgehen, dass die vorherige Zeile erfolgreich war. Dies hat den zusätzlichen Vorteil, dass Code erstellt wird, der viel einfacher zu lesen ist. Haben Sie jemals eine API-Referenz mit Codebeispielen gesehen, die den Kommentar "// Fehlerbehandlung aus Gründen der Übersichtlichkeit weggelassen" enthielt? Wenn sie die Fehlerbehandlung in der Dokumentation entfernen, um das Lesen des Beispiels zu erleichtern, wäre es nicht großartig, wenn Sie dasselbe im Produktionscode tun und das Lesen auch vereinfachen könnten? Genau das können Sie mit Ausnahmen tun.

Jetzt wird mindestens eine Person, die diesen Beitrag liest, sagen: "pffttt ein Noob weiß nicht, wie man codiert. Ich vergesse nie die Fehlerbehandlung." a) Gut für Sie, aber was noch wichtiger ist: b) Wie ich oben sagte, erfordert die Codierung, dass sich Ihr Gehirn konzentriert und nur so viel Gehirn herumläuft. das gilt für ALLE. Wenn Ihr Code leicht zu lesen ist, ist das weniger Konzentration. Wenn Sie try/catch um einen großen Teil legen und dann einfach die Anwendungslogik codieren können, ist das weniger Konzentration. Jetzt können Sie diese zusätzliche Kapazität für wichtigere Dinge nutzen, z. B. um die eigentliche Arbeit viel schneller zu erledigen.

16
DXM

Vorteile des Auslösens von Ausnahmen gegenüber der Rückgabe von Fehlercodes:

  • Der Rückgabewert einer Funktion kann für das verwendet werden, wofür sie entwickelt wurde.: Gibt das Ergebnis des Funktionsaufrufs zurück, keinen Code, der den Erfolg oder einen von vielen Fehlern innerhalb der Funktion darstellt. Dies schafft saubereren und eleganteren Code. weniger Ausgabe-/Referenzparameter, Zuordnungen werden zu Dingen vorgenommen, die Ihnen wirklich wichtig sind, und nicht zu Wegwerfstatuscodes usw.
  • Ausnahmen sind objektorientiert, daher haben Ausnahmetypen eine wiederverwendbare konzeptionelle Bedeutung.. Was bedeutet ein Rückkehrcode von -2? Sie müssen es in der Dokumentation nachschlagen (was besser gut sein sollte oder Sie sind gezwungen, Code zu verfolgen, und wenn Sie keinen Quellcode haben, sind Sie wirklich durcheinander). Was bedeutet eine NullPointerException? Dass Sie versucht haben, eine Methode für eine Variable aufzurufen, die nicht auf eine Instanz verweist. Darüber hinaus kapseln Ausnahmen Daten, sodass Sie genau wissen, wie und wo Ihr Programm auf leicht lesbare Weise Fehler gemacht hat. Ein Rückkehrcode kann Ihnen nur sagen, welche Methode fehlerhaft ist.
  • Standardmäßig werden Rückkehrcodes ignoriert; Ausnahmen nicht. Wenn Sie den Rückkehrcode nicht speichern und überprüfen, fährt Ihr Programm fröhlich fort, beschädigt Daten, vermasselt Zeiger und zerknittert sich im Allgemeinen selbst in Müll. Wenn Sie keine Ausnahme abfangen, wird diese an das Betriebssystem ausgegeben, das Ihr Programm beendet. "Schnell scheitern".
  • Ausnahmen lassen sich leichter standardisieren. Da Ausnahmen mehr selbstdokumentierenden Code erzeugen und die meisten von uns nicht alle Programme zu 100% von Grund auf neu schreiben, sind bereits Ausnahmetypen verfügbar, die eine Vielzahl allgemeiner Fälle abdecken. Die billigste Lösung ist die, die Sie bereits haben. Daher werden diese integrierten Ausnahmetypen in Situationen verwendet, die denen ähneln, für die sie ursprünglich erstellt wurden. Eine InvalidOperationException bedeutet nun, dass Sie versucht haben, etwas zu tun, das nicht mit dem aktuellen Status eines Objekts übereinstimmt (genau das, was in der Fehlermeldung angegeben ist), unabhängig davon, welche Funktion aus welcher Drittanbieter-Bibliothek Sie aufrufen wollten.

    Vergleichen Sie das mit den Rückkehrcodes. Bei einer Methode kann -1 ein "Nullzeigerfehler" sein, während -2 ein "ungültiger Operationsfehler" sein kann. Bei der nächsten Methode wurde zuerst die Bedingung "ungültige Operation" überprüft, sodass der Fehler den Rückkehrcode -1 erhielt, während der "Nullzeiger" -2 erhielt. Eine Zahl ist eine Zahl, und Sie können jeden Standard für die Zuweisung von Zahlen zu Fehlern erstellen, ebenso wie alle anderen, was zu einer Menge konkurrierender Standards führt.

  • Ausnahmen erlauben es Ihnen, optimistischer zu sein. Anstatt pessimistisch alles zu überprüfen, was mit einer Anweisung schief gehen könnte, bevor die Anweisung tatsächlich ausgeführt wird, können Sie einfach try Ausführen der Anweisung und Abfangen aller von ihr generierten Ausnahmen. Wenn Sie die Fehlerursache beheben und es erneut versuchen können, ist dies in Ordnung, wenn nicht. Sie hätten wahrscheinlich nichts dagegen tun können, wenn Sie es vorher gewusst hätten.

    Die ausnahmebasierte Fehlerbehandlung erstellt somit effizienteren Code, indem davon ausgegangen wird, dass er funktioniert, bis er nicht mehr funktioniert. Guard-Anweisungen, die die Zeit des frühen Kostenverarbeiters zurückgeben; Sie sollten daher in erster Linie verwendet werden, wenn die Vorteile einer Überprüfung im Voraus die Kosten überwiegen. Wenn Sie in wenigen Takten feststellen können, dass die Eingabe niemals eine gültige Ausgabe erzeugt, es jedoch einige Sekunden dauert, bis der Hauptteil der Funktion zu demselben Ergebnis kommt, setzen Sie auf jeden Fall eine Schutzklausel ein. Wenn jedoch ein Dutzend Dinge schief gehen könnten, von denen keines besonders wahrscheinlich ist und von denen die meisten eine teure Ausführung erfordern, ist der "glückliche Pfad" der normalen Programmausführung um ein Vielfaches schneller, wenn Sie einfach versuchen/fangen.

6
KeithS

Der grundlegende Unterschied für mich ist Lesen und Schreiben:

Die Behandlung von Fehlercodes führt dazu, dass die Absicht unübersichtlich wird: Ausnahmebasierter Code ist normalerweise einfacher zu lesen, da sich die Quelle auf die Dinge konzentriert, die sollte passieren, anstatt was könnte.

OTOH, Viele ausnahmebasierte Codes sind schwieriger zu schreiben, da Sie für eine Korrektheitsanalyse über "nicht verwandte" Details wie Bau-/Zerstörungsreihenfolge, teilweise konstruierte Objekte, Speicherbereinigung usw. streiten müssen.

Wie schwer? So schwer.

Das Generieren aussagekräftiger Ausnahmen kann auch die Quelle überladen. Ich habe Codebasen, in denen praktisch jeder Codezeile zwei oder drei Zeilen folgen, die eine literarische Ausnahme bilden.

Um sinnvolle Ausnahmen auf die oberste Ebene zu übertragen, müssen diese häufig neu verpackt werden. Wenn ich auf "Rebicker-Zellen" klicke, ist eine Fehlermeldung wie "Freigabeverletzung für% tmp%\foo\file536821" nicht viel hilfreicher als "Fehler 77" .


Ich habe wirklich keine Präferenz, beide Optionen sind gleich hässlich. Was noch schlimmer ist: Welches besser ist, scheint auf schwer vorhersehbare Weise stark vom Projekt abzuhängen. Nach meiner Erfahrung ist die Hardware-Interaktion auf niedriger Ebene mit Fehlercodes reibungsloser (möglicherweise die Clients ...). Der Datenzugriff auf niedriger Ebene ist mit Ausnahmen besser.

5
peterchen

Lassen Sie uns die Implementierungsprobleme vieler Sprachen beiseite legen. Die Vorteilsausnahmen bestehen darin, dass sie zentralisiert werden können, um alle Arten (theoretisch) von Ausnahmezuständen des Programms zu behandeln. Das Problem, mit dem sie sich befassen, ist, dass außergewöhnliche Dinge passieren und kaum jemals vorhergesagt werden können. Was (theoretisch) mit Ausnahmen großartig ist, ist, dass Sie sicher sind, solange Sie:

  • Schreiben Sie einen ausnahmesicheren Code so weit wie möglich
  • Fangen Sie die Ausnahmezustände über Ausnahmen ab
  • Behandeln Sie die Ausnahmen entsprechend
3
zxcdw

Ausnahmebasierter Code verschiebt die Fehlerbehandlung aus dem Hauptprogrammablauf. Stattdessen wird die Fehlerbehandlung entweder in den Objektdestruktor oder in das catch-Konstrukt verschoben.

Der größte Vorteil und auch der größte Nachteil mit Ausnahme ist, dass es zwingt Sie nicht zum Nachdenken über die Fehlerbehandlung. Mit Ausnahmen schrieben Sie oft nur einfachen Code und ließen den Destruktor seine Arbeit erledigen und vergaßen das eine kleine Szenario, das der Destruktor nicht handhabt und manuell ausführen muss (was klar dokumentiert ist und daher völlig Ihre eigene Schuld ist zum Vergessen). Das gleiche Szenario kann mit Fehlercode auftreten, aber wenn Sie Code mit Fehlercode schreiben, müssen Sie das Handbuch/die Dokumentation/die Quelle ständig überprüfen, und es ist weniger wahrscheinlich, dass Sie diese eine kleine Bedingung vergessen.

Ja, diese mittleren Ausnahmen erschweren manchmal das Schreiben von korrektem Code.

Das Schreiben eines schlechten Codes mit Fehlercode ist einfach, das Schreiben eines guten Codes mit Fehlercode ist schwierig. Das Schreiben eines anständigen Codes in Ausnahmefällen ist einfach (da Fehler das Programm beenden oder von sehr hochrangigen Handlern abgefangen werden, die vor dem bevorstehenden Untergang warnen könnten, anstatt stillschweigend herumgereicht zu werden), ist das Schreiben von gutem Code mit Ausnahmen wirklich schwierig (weil Sie behandeln die Fehler mit einem Arm von dem Ort entfernt, an dem der Fehler auftritt.

2
Lie Ryan
  • Leicht zu vergessende Exit-Codes
  • Doppelter Code. Das Überprüfen des Fehlercodes und das Zurückgeben des Fehlercodes bei einem Fehler würde dazu führen, dass viel doppelter Code verarbeitet und viele geändert werden, wenn sich der Rückgabetyp ändert. Was tun, wenn verschiedene Typen einen Fehler verursachen können?
  • Ausnahmen werden außerhalb des Codepfads ausgeführt, was bedeutet, dass der Rückgabewert nicht überprüft werden muss. Aber irgendwo in der Binärdatei wird gearbeitet, wenn der Ausnahmecode ausgeführt wird.
  • Leicht zu erkennen, welche Funktionen welche Art von Fehlern behandeln. Sie müssen nicht viel Code durchsehen, sondern nur durch catch scrollen.
  • Weitere Details (in bestimmten Sprachen). Es hat Informationen eingebaut, wie z. B. welche Zeile, Funktion usw. Diese in eine Fehlerklasse zu setzen, wäre mühsam. Wahrscheinlich würden Sie nur den Funktionsnamen und hoffentlich den Typ oder die Ursache des Fehlers schreiben.
  • Debugger verstehen, dass Ausnahmen Fehler sind, und können pausieren, wenn sie beim Debuggen auf eine treffen. Tools helfen :)
1
user2528