Es sind viele Anforderungen erforderlich, damit ein System Ausnahmen ordnungsgemäß übermittelt und behandelt. Es gibt auch viele Optionen für eine Sprache zur Auswahl, um das Konzept umzusetzen.
Voraussetzungen für Ausnahmen (in keiner bestimmten Reihenfolge):
Dokumentation : Eine Sprache sollte ein Mittel haben, um Ausnahmen zu dokumentieren, die eine API auslösen kann. Idealerweise sollte dieses Dokumentationsmedium maschinenverwendbar sein, damit Compiler und IDEs den Programmierer unterstützen können.
Außergewöhnliche Situationen übertragen : Dies ist offensichtlich, damit eine Funktion Situationen übermitteln kann, die verhindern, dass die aufgerufene Funktionalität die erwartete Aktion ausführt. Meiner Meinung nach gibt es drei große Kategorien solcher Situationen:
2.1 Fehler im Code, die dazu führen, dass einige Daten ungültig sind.
2.2 Probleme bei der Konfiguration oder anderen externen Ressourcen.
2.3 Ressourcen, die von Natur aus unzuverlässig sind (Netzwerk, Dateisysteme, Datenbanken, Endbenutzer usw.). Dies ist ein Eckpfeiler, da wir aufgrund ihrer Unzuverlässigkeit mit ihren sporadischen Fehlern rechnen müssen. In diesem Fall sind diese Situationen als außergewöhnlich anzusehen?
Stellen Sie genügend Informationen bereit, damit der Code damit umgehen kann : Die Ausnahmen sollten dem Angerufenen ausreichende Informationen zur Verfügung stellen, damit er reagieren und möglicherweise mit der Situation umgehen kann. Die Informationen sollten auch ausreichend sein, damit diese Ausnahmen bei der Protokollierung einem Programmierer genügend Kontext bieten, um die fehlerhaften Anweisungen zu identifizieren, zu isolieren und eine Lösung bereitzustellen.
Geben Sie dem Programmierer Vertrauen in den aktuellen Status des Ausführungsstatus seines Codes : Die Ausnahmebehandlungsfunktionen eines Softwaresystems sollten ausreichend vorhanden sein, um die erforderlichen Sicherheitsvorkehrungen zu treffen und dem Programmierer aus dem Weg zu gehen, damit er sich auf die Aufgabe konzentrieren kann Hand.
Um diese abzudecken, wurden die folgenden Methoden in verschiedenen Sprachen implementiert:
Überprüfte Ausnahmen Bieten eine hervorragende Möglichkeit, Ausnahmen zu dokumentieren, und sollten bei korrekter Implementierung theoretisch ausreichend Sicherheit bieten, dass alles in Ordnung ist. Die Kosten sind jedoch so hoch, dass viele es für produktiver halten, Ausnahmen einfach zu umgehen, indem sie sie verschlucken oder als ungeprüfte Ausnahmen erneut auslösen. Wenn unangemessen geprüfte Ausnahmen verwendet werden, verliert dies so ziemlich alle Nützlichkeit. Außerdem erschweren geprüfte Ausnahmen das Erstellen einer zeitstabilen API. Die Implementierung eines generischen Systems innerhalb einer bestimmten Domäne bringt eine Menge außergewöhnlicher Situationen mit sich, die mit ausschließlich geprüften Ausnahmen nur schwer aufrechtzuerhalten sind.
Nicht aktivierte Ausnahmen - viel vielseitiger als aktivierte Ausnahmen dokumentieren sie die möglichen Ausnahmesituationen einer bestimmten Implementierung nicht richtig. Sie stützen sich, wenn überhaupt, auf Ad-hoc-Dokumentation. Dies führt zu Situationen, in denen die Unzuverlässigkeit eines Mediums durch eine API maskiert wird, die den Anschein von Zuverlässigkeit erweckt. Auch wenn diese Ausnahmen ausgelöst werden, verlieren sie ihre Bedeutung, wenn sie sich wieder durch die Abstraktionsschichten bewegen. Da sie schlecht dokumentiert sind, kann ein Programmierer sie nicht gezielt ansprechen und muss häufig ein viel breiteres Netz als erforderlich bereitstellen, um sicherzustellen, dass sekundäre Systeme im Falle eines Ausfalls nicht das gesamte System herunterfahren. Damit kommen wir gleich wieder auf das Problem des Schluckproblems zurück.
Rückgabetypen mit mehreren Zuständen Hier muss auf eine disjunkte Menge, ein Tupel oder ein ähnliches Konzept zurückgegriffen werden, um entweder das erwartete Ergebnis oder ein Objekt zurückzugeben, das die Ausnahme darstellt. Hier kein Abwickeln des Stapels, kein Durchschneiden des Codes, alles wird normal ausgeführt, aber der Rückgabewert muss vor dem Fortfahren auf Fehler überprüft werden. Ich habe noch nicht wirklich damit gearbeitet, kann also aus Erfahrung keinen Kommentar abgeben. Ich gebe zu, dass es einige Probleme löst, Ausnahmen, die den normalen Fluss umgehen, aber es wird immer noch unter den gleichen Problemen leiden wie die überprüften Ausnahmen, da es lästig und ständig "in deinem Gesicht" ist.
Die Frage ist also:
Welche Erfahrungen haben Sie in dieser Angelegenheit gemacht und was ist Ihrer Meinung nach der beste Kandidat, um ein gutes Ausnahmebehandlungssystem für eine Sprache zu entwickeln?
EDIT: Wenige Minuten nach dem Schreiben dieser Frage bin ich auf diesen Beitrag gestoßen, gruselig!
quelle
noexcept
Geschichte in C ++ kann auch für EH in C # und Java sehr gute Einblicke liefern.)Antworten:
In den frühen Tagen von C ++ stellten wir fest, dass stark typisierte Sprachen ohne irgendeine generische Programmierung extrem unhandlich waren. Wir haben auch festgestellt, dass geprüfte Ausnahmen und generische Programmierung nicht gut zusammenarbeiten und geprüfte Ausnahmen im Wesentlichen aufgegeben wurden.
Multiset-Rückgabetypen sind großartig, aber kein Ersatz für Ausnahmen. Der Code ist ausnahmslos voller Fehler bei der Fehlerprüfung.
Das andere Problem bei aktivierten Ausnahmen besteht darin, dass eine Änderung der Ausnahmen, die von einer Funktion auf niedriger Ebene ausgelöst wird, eine Kaskade von Änderungen bei allen Anrufern und ihren Anrufern usw. erzwingt. Die einzige Möglichkeit, dies zu verhindern, besteht darin, dass jede Codeebene Ausnahmen abfängt, die von niedrigeren Ebenen ausgelöst werden, und sie in eine neue Ausnahme einschließt. Auch hier erhalten Sie sehr lauten Code.
quelle
Die Verwendung von Ausnahmen war lange Zeit der De-facto-Standard für die Übermittlung von Fehlern. Funktionale Programmiersprachen bieten jedoch die Möglichkeit eines anderen Ansatzes, z. B. der Verwendung von Monaden (die ich nicht verwendet habe) oder der leichteren "Eisenbahnorientierten Programmierung", wie von Scott Wlaschin beschrieben.
In diesem Blogbeitrag
In dieser Präsentation auf der NDC 2014
Es ist wirklich eine Variante des mehrstufigen Ergebnistyps.
Der Ergebnistyp könnte so deklariert werden
Das Ergebnis einer Funktion, die diesen Typ zurückgibt, wäre also entweder ein
Success
oder einFail
Typ. Es kann nicht beides sein.In imperativ orientierten Programmiersprachen kann diese Art von Stil eine große Menge Code auf der Aufruferseite erfordern. Mit der funktionalen Programmierung können Sie jedoch Bindungsfunktionen oder Operatoren erstellen, um mehrere Funktionen miteinander zu verknüpfen, sodass die Fehlerprüfung nicht die Hälfte des Codes beansprucht. Als Beispiel:
Die
updateUser
Funktion ruft jede dieser Funktionen nacheinander auf, und jede von ihnen kann fehlschlagen. Wenn alle erfolgreich sind, wird das Ergebnis der zuletzt aufgerufenen Funktion zurückgegeben. Wenn eine der Funktionen fehlschlägt, ist das Ergebnis dieser Funktion das Ergebnis der GesamtfunktionupdateUser
. Dies alles wird vom benutzerdefinierten Operator >> = erledigt.Im obigen Beispiel könnten die Fehlertypen sein
Wenn der Aufrufer von
updateUser
nicht alle möglichen Fehler der Funktion explizit behandelt, gibt der Compiler eine Warnung aus. Sie haben also alles dokumentiert.In Haskell gibt es eine
do
Notation, die den Code noch sauberer machen kann.quelle
do
Notation von Haskell erwähnen , wodurch der resultierende Code noch sauberer wird.Railway Oriented Programming
ist genau monadisches Verhalten.Ich finde Petes Antwort sehr gut und möchte einige Überlegungen und ein Beispiel hinzufügen. Eine sehr interessante Diskussion über die Verwendung von Ausnahmen im Vergleich zur Rückgabe spezieller Fehlerwerte findet sich in Programming in Standard ML von Robert Harper am Ende von Abschnitt 29.3, Seite 243, 244.
Das Problem besteht darin, eine Teilfunktion zu implementieren,
f
die einen Wert eines Typs zurückgibtt
. Eine Lösung besteht darin, die Funktion vom Typ zu habenund eine Ausnahme auslösen, wenn kein mögliches Ergebnis vorliegt. Die zweite Lösung besteht darin, eine Funktion mit Typ zu implementieren
und Rückkehr
SOME v
zum Erfolg undNONE
zum Misserfolg.Hier ist der Text aus dem Buch, mit einer kleinen Anpassung, die ich vorgenommen habe, um den Text allgemeiner zu gestalten (das Buch bezieht sich auf ein bestimmtes Beispiel). Der geänderte Text ist kursiv geschrieben .
Dies betrifft die Wahl zwischen Ausnahmen und Optionsrückgabetypen.
In Bezug auf die Idee, dass die Darstellung eines Fehlers im Rückgabetyp zu Fehlerprüfungen führt, die über den gesamten Code verteilt sind, muss dies nicht der Fall sein. Hier ist ein kleines Beispiel in Haskell, das dies veranschaulicht.
Angenommen, wir möchten zwei Zahlen analysieren und dann die erste durch die zweite teilen. Es kann also ein Fehler beim Parsen jeder Zahl oder beim Teilen (Division durch Null) auftreten. Wir müssen also nach jedem Schritt nach einem Fehler suchen.
Das Parsen und die Division werden im
let ...
Block durchgeführt. Beachten Sie, dass bei Verwendung derMaybe
Monade und derdo
Notation nur der Erfolgspfad angegeben wird: Die Semantik derMaybe
Monade verbreitet implizit den Fehlerwert (Nothing
). Kein Overhead für den Programmierer.quelle
Either
Typ besser geeignet. Was machst du, wenn duNothing
hier bist ? Sie erhalten nur die Meldung "Fehler". Nicht sehr hilfreich zum Debuggen.Ich bin ein großer Fan von Checked Exceptions geworden und möchte meine allgemeine Regel darüber teilen, wann ich sie verwenden soll.
Ich bin zu dem Schluss gekommen, dass es grundsätzlich zwei Arten von Fehlern gibt, mit denen sich mein Code befassen muss. Es gibt Fehler, die vor der Ausführung des Codes getestet werden können, und es gibt Fehler, die vor der Ausführung des Codes nicht testbar sind. Ein einfaches Beispiel für einen Fehler, der getestet werden kann, bevor der Code in einer NullPointerException ausgeführt wird.
Ein einfacher Test hätte den Fehler vermeiden können, wie ...
Es gibt Zeiten im Computer, in denen Sie einen oder mehrere Tests ausführen können, bevor Sie den Code ausführen, um sicherzustellen, dass Sie sicher sind, UND SIE ERHALTEN NOCH EINE AUSNAHME. Sie können beispielsweise ein Dateisystem testen, um sicherzustellen, dass auf der Festplatte genügend Speicherplatz vorhanden ist, bevor Sie Ihre Daten auf das Laufwerk schreiben. In einem Multiprozessor-Betriebssystem, wie es heute verwendet wird, könnte Ihr Prozess auf Speicherplatz testen, und das Dateisystem gibt einen Wert zurück, der besagt, dass genügend Speicherplatz vorhanden ist. Anschließend kann ein Kontextwechsel zu einem anderen Prozess die verbleibenden Bytes schreiben, die für den Betrieb verfügbar sind System. Wenn der Betriebssystemkontext zu Ihrem laufenden Prozess zurückkehrt, in dem Sie Ihre Inhalte auf die Festplatte schreiben, tritt eine Ausnahme auf, einfach weil nicht genügend Speicherplatz im Dateisystem vorhanden ist.
Ich betrachte das obige Szenario als perfekten Fall für eine überprüfte Ausnahme. Es ist eine Ausnahme im Code, die Sie dazu zwingt, mit etwas Schlechtem umzugehen, obwohl Ihr Code perfekt geschrieben werden könnte. Wenn Sie schlechte Dinge wie "Schlucken Sie die Ausnahme" tun, sind Sie der schlechte Programmierer. Übrigens habe ich Fälle gefunden, in denen es sinnvoll ist, die Ausnahme zu schlucken, aber bitte hinterlassen Sie im Code einen Kommentar, warum die Ausnahme verschluckt wurde. Der Ausnahmebehandlungsmechanismus ist nicht schuld. Ich scherze gewöhnlich, dass ich es vorziehen würde, wenn mein Herzschrittmacher in einer Sprache geschrieben wird, in der Ausnahmen überprüft wurden.
Es gibt Zeiten, in denen es schwierig wird zu entscheiden, ob der Code testbar ist oder nicht. Wenn Sie beispielsweise einen Interpreter schreiben und eine SyntaxException ausgelöst wird, wenn der Code aus syntaktischen Gründen nicht ausgeführt werden kann, sollte die SyntaxException eine geprüfte Ausnahme oder (in Java) eine RuntimeException sein? Ich würde antworten, wenn der Interpreter die Syntax des Codes überprüft, bevor der Code ausgeführt wird, dann sollte die Ausnahme eine RuntimeException sein. Wenn der Interpreter einfach den Code 'hot' ausführt und einfach einen Syntaxfehler feststellt, würde ich sagen, dass die Ausnahme eine überprüfte Ausnahme sein sollte.
Ich gebe zu, dass ich nicht immer glücklich bin, eine überprüfte Ausnahme fangen oder werfen zu müssen, weil ich manchmal nicht sicher bin, was ich tun soll. Aktivierte Ausnahmen sind eine Möglichkeit, einen Programmierer zu zwingen, sich des potenziellen Problems bewusst zu werden, das auftreten kann. Einer der Gründe, warum ich in Java programmiere, ist, dass es geprüfte Ausnahmen gibt.
quelle
Ich bin derzeit mitten in einem ziemlich großen OOP-basierten Projekt / API und habe dieses Layout der Ausnahmen verwendet. Aber es hängt wirklich davon ab, wie tief Sie mit der Ausnahmebehandlung und dergleichen gehen möchten.
ExpectedException
- AuthorisedException
- EmptySetException
- NoRemainingException
- NoRowsException
- NotFoundException
- ValidationException
UnexpectedException
- ConnectivityException
- EnvironmentException
- ProgrammerException
- SQLException
BEISPIEL
quelle
NAN
oderNULL
.