Checked vs Unchecked vs No Exception… Eine bewährte Methode für gegensätzliche Überzeugungen

10

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):

  1. 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.

  2. 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?

  3. 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.

  4. 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:

  1. Ü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.

  2. 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.

  3. 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!

Newtopian
quelle
2
"Es wird unter den gleichen Problemen leiden wie die überprüften Ausnahmen, da es lästig und ständig in Ihrem Gesicht ist": Nicht wirklich: Mit der richtigen Sprachunterstützung müssen Sie nur den "Erfolgspfad" programmieren, wobei sich die zugrunde liegende Sprachmaschinerie um die Verbreitung kümmert Fehler.
Giorgio
"Eine Sprache sollte ein Mittel haben, um Ausnahmen zu dokumentieren, die eine API auslösen kann." - weeeel. In C ++ "haben" wir gelernt, dass dies nicht wirklich funktioniert. Alles , was Sie können wirklich sinnvoll ist , in dem Zustand tun , ob ein API werfen kann jede Ausnahme. (Das ist wirklich eine kurze Geschichte, aber ich denke, ein Blick auf die noexceptGeschichte in C ++ kann auch für EH in C # und Java sehr gute Einblicke liefern.)
Martin Ba

Antworten:

10

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.

Kevin Cline
quelle
2
Generika helfen bei der Lösung einer ganzen Klasse von Fehlern, die hauptsächlich auf eine Einschränkung der Unterstützung der Sprache für das OO-Paradigma zurückzuführen sind. Dennoch scheinen die Alternativen darin zu bestehen, entweder Code zu haben, der meistens Fehlerprüfungen durchführt, oder der in der Hoffnung ausgeführt wird, dass nie etwas schief geht. Entweder haben Sie ständig Ausnahmesituationen im Gesicht oder Sie leben in einem Traumland von flauschigen weißen Hasen, die wirklich hässlich werden, wenn Sie einen großen bösen Wolf in die Mitte fallen lassen!
Newtopian
3
+1 für das Kaskadenproblem. Jedes System / jede Architektur, die Änderungen erschwert, führt nur zu Affen-Patches und chaotischen Systemen, egal wie gut die Autoren sie entworfen haben.
Matthieu M.
2
@Newtopian: Vorlagen erledigen Dinge, die in strikter Objektorientierung nicht möglich sind, z. B. statische Typensicherheit für generische Container.
David Thornley
2
Ich würde gerne ein Ausnahmesystem mit einem Konzept von "geprüften Ausnahmen" sehen, das sich jedoch stark von dem von Java unterscheidet. Karo-ness sollte kein Attribut einer Ausnahme sein Typ , sondern werfen Websites, catch - Sites und Ausnahme - Instanzen; Wenn eine Methode als Auslösen einer geprüften Ausnahme angekündigt wird, sollte dies zwei Auswirkungen haben: (1) Die Funktion sollte einen "Auswurf" der geprüften Ausnahme behandeln, indem sie bei der Rückkehr etwas Besonderes tut (z. B. das Setzen des Übertragsflags usw. abhängig von der genaue Plattform), auf die der Rufcode vorbereitet werden müsste.
Supercat
7
"Der Code ist ausnahmslos voller Fehler bei der Fehlerprüfung.": Da bin ich mir nicht sicher: In Haskell können Sie dafür Monaden verwenden, und alle Fehler bei der Fehlerprüfung sind verschwunden. Das durch "Multistate Return-Typen" verursachte Rauschen ist eher eine Einschränkung der Programmiersprache als der Lösung an sich.
Giorgio
9

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.

Es ist wirklich eine Variante des mehrstufigen Ergebnistyps.

  • Eine Funktion gibt entweder einen Erfolg oder einen Fehler zurück. Es kann nicht beide zurückgeben (wie dies bei einem Tupel der Fall ist).
  • Alle möglichen Fehler wurden kurz und bündig dokumentiert (zumindest in F # mit Ergebnistypen als diskriminierte Gewerkschaften).
  • Der Anrufer kann das Ergebnis nicht verwenden, ohne zu berücksichtigen, ob das Ergebnis erfolgreich war oder fehlgeschlagen ist.

Der Ergebnistyp könnte so deklariert werden

type Result<'TSuccess,'TFail> =
| Success of 'TSuccess
| Fail of 'TFail

Das Ergebnis einer Funktion, die diesen Typ zurückgibt, wäre also entweder ein Successoder ein FailTyp. 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:

// Create an updateUser function that takes an id, and new state
// as input, and updates an existing user.
let updateUser id input =
    validateInput input
    >>= loadUser id
    >>= updateUser input
    >>= saveUser id
    >>= notifyAboutUserUpdated

Die updateUserFunktion 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 Gesamtfunktion updateUser. Dies alles wird vom benutzerdefinierten Operator >> = erledigt.

Im obigen Beispiel könnten die Fehlertypen sein

type UserValidationErrorType =
| InvalidEmail of string
| MissingFirstName of string
... etc

type DbErrorType =
| RecordNotFound of int
| ConcurrencyError of int

type UpdateUserErrorType =
| InvalidInput of UserValidationErrorType
| DbError of DbErrorType

Wenn der Aufrufer von updateUsernicht alle möglichen Fehler der Funktion explizit behandelt, gibt der Compiler eine Warnung aus. Sie haben also alles dokumentiert.

In Haskell gibt es eine doNotation, die den Code noch sauberer machen kann.

Pete
quelle
2
Sehr gute Antwort und Referenzen (bahnorientierte Programmierung), +1. Vielleicht möchten Sie die doNotation von Haskell erwähnen , wodurch der resultierende Code noch sauberer wird.
Giorgio
1
@Giorgio - Ich habe es jetzt getan, aber ich habe nicht mit Haskell gearbeitet, nur mit F #, also konnte ich nicht wirklich viel darüber schreiben. Aber Sie können die Antwort ergänzen, wenn Sie möchten.
Pete
Vielen Dank, ich habe ein kleines Beispiel geschrieben, aber da es nicht klein genug war, um zu Ihrer Antwort hinzugefügt zu werden, habe ich eine vollständige Antwort geschrieben (mit einigen zusätzlichen Hintergrundinformationen).
Giorgio
2
Das Railway Oriented Programmingist genau monadisches Verhalten.
Daenyth
5

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, fdie einen Wert eines Typs zurückgibt t. Eine Lösung besteht darin, die Funktion vom Typ zu haben

f : ... -> t

und eine Ausnahme auslösen, wenn kein mögliches Ergebnis vorliegt. Die zweite Lösung besteht darin, eine Funktion mit Typ zu implementieren

f : ... -> t option

und Rückkehr SOME vzum Erfolg und NONEzum 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 .

Was sind die Kompromisse zwischen den beiden Lösungen?

  1. Die auf Optionstypen basierende Lösung macht im Funktionstyp fdie Möglichkeit eines Ausfalls deutlich. Dies zwingt den Programmierer, anhand einer Fallanalyse des Ergebnisses des Aufrufs explizit auf Fehler zu testen. Die Typprüfung stellt sicher, dass man nicht dort verwenden kann, t optionwo at erwartet wird. Die auf Ausnahmen basierende Lösung weist nicht explizit auf einen Fehler in ihrem Typ hin. Der Programmierer ist jedoch gezwungen, den Fehler zu behandeln, da sonst zur Laufzeit und nicht zur Kompilierungszeit ein nicht erfasster Ausnahmefehler ausgelöst würde.
  2. Die auf Optionstypen basierende Lösung erfordert eine explizite Fallanalyse des Ergebnisses jedes Aufrufs. Wenn „die meisten“ Ergebnisse erfolgreich sind, ist die Prüfung redundant und daher übermäßig kostspielig. Die auf Ausnahmen basierende Lösung ist frei von diesem Overhead: Sie ist eher auf den „normalen“ Fall der Rückgabe eines tals auf den „Fehler“ -Fall der Rückgabe eines Ergebnisses ausgerichtet . Die Implementierung von Ausnahmen stellt sicher, dass die Verwendung eines Handlers effizienter ist als eine explizite Fallanalyse, wenn ein Fehler im Vergleich zum Erfolg selten ist.

[cut] Wenn Effizienz im Vordergrund steht, bevorzugen wir im Allgemeinen Ausnahmen, wenn Fehler selten sind, und Optionen, wenn Fehler relativ häufig auftreten. Wenn andererseits die statische Prüfung von größter Bedeutung ist, ist es vorteilhaft, Optionen zu verwenden, da die Typprüfung die Anforderung erzwingt, dass der Programmierer auf Fehler prüft, anstatt dass der Fehler nur zur Laufzeit auftritt.

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.

import Text.Read

parseInt :: String -> Maybe Int
parseInt s = readMaybe s :: Maybe Int

safeDiv :: Int -> Int -> Maybe Int
safeDiv n d = if d /= 0 then Just (n `div` d) else Nothing

toString :: Maybe Int -> String
toString (Just i) = show i
toString Nothing  = "error"

main = do
         -- Get two lines from the terminal.
         nStr <- getLine
         dStr <- getLine

         -- Parse each string and divide.
         let r = do n <- parseInt nStr
                    d <- parseInt dStr
                    safeDiv n d

         -- Print the result.
         putStrLn $ toString r

Das Parsen und die Division werden im let ...Block durchgeführt. Beachten Sie, dass bei Verwendung der MaybeMonade und der doNotation nur der Erfolgspfad angegeben wird: Die Semantik der MaybeMonade verbreitet implizit den Fehlerwert ( Nothing). Kein Overhead für den Programmierer.

Giorgio
quelle
2
Ich denke, in solchen Fällen, in denen Sie eine nützliche Fehlermeldung drucken möchten, ist der EitherTyp besser geeignet. Was machst du, wenn du Nothinghier bist ? Sie erhalten nur die Meldung "Fehler". Nicht sehr hilfreich zum Debuggen.
Sara
1

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.

//... bad code below.  the runnable variable
// tries to call the run() method before the variable
// is instantiated.  Running the code below will cause
// a NullPointerException.
Runnable runnable = null;
runnable.run();

Ein einfacher Test hätte den Fehler vermeiden können, wie ...

Runnable runnable = null;
...
if (runnable != null)
{   runnable.run(); }

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.

James Moliere
quelle
1
Ich würde es vorziehen, wenn mein Herzschrittmacher in einer Sprache geschrieben wäre, in der es überhaupt keine Ausnahmen gab und in allen Codezeilen Fehler durch Rückkehrcodes behandelt wurden. Wenn Sie eine Ausnahme auslösen, sagen Sie "es ist alles schief gelaufen" und der einzig sichere Weg, um die Verarbeitung fortzusetzen, besteht darin, anzuhalten und neu zu starten. Ein Programm, das so leicht in einem ungültigen Zustand endet, ist nicht das, was Sie für kritische Software wollen (und Java verbietet ausdrücklich seine Verwendung für kritische Software in der EULA)
gbjbaanb
Wenn Sie eine Ausnahme verwenden und sie nicht überprüfen, während Sie den Rückkehrcode verwenden und sie am Ende nicht überprüfen, ergibt sich der gleiche Herzstillstand.
Newtopian
-1

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

   $valid_types = array('mysql', 'oracle', 'sqlite');
       if (!in_array($type, $valid_types)) {
           throw new ecProgrammerException(
        'The database type specified, %1$s, is invalid. Must be one of: %2$s.',
    $type,
    join(', ', $valid_types)
    );
}
MattyD
quelle
11
Wenn die Ausnahme erwartet wird, ist es nicht wirklich eine Ausnahme. "NoRowsException"? Klingt für mich nach Kontrollfluss und daher nach einer schlechten Verwendung einer Ausnahme.
Quentin-Starin
1
@qes: Es ist sinnvoll, eine Ausnahme auszulösen, wenn eine Funktion keinen Wert berechnen kann, z. B. double Math.sqrt (double v) oder User findUser (lange ID). Dies gibt dem Anrufer die Freiheit, Fehler zu fangen und zu behandeln, wo es zweckmäßig ist, anstatt sie nach jedem Anruf zu überprüfen.
Kevin Cline
1
Erwartet = Kontrollfluss = Anti-Muster der Ausnahme. Eine Ausnahme sollte nicht für den Kontrollfluss verwendet werden. Wenn erwartet wird, dass für eine bestimmte Eingabe ein Fehler auftritt, wird dieser nur als Teil des Rückgabewerts übergeben. Also haben wir NANoder NULL.
Eonil
1
@Eonil ... oder Option <T>
Maarten Bodewes