it-swarm.com.de

Ausnahmen: Warum früh werfen? Warum spät fangen?

Es gibt viele bekannte Best Practices für die isolierte Behandlung von Ausnahmen. Ich kenne die "Do's and Don'ts" gut genug, aber die Dinge werden kompliziert, wenn es um Best Practices oder Muster in größeren Umgebungen geht. "Früh werfen, spät fangen" - das habe ich schon oft gehört und es verwirrt mich immer noch.

Warum sollte ich früh werfen und spät fangen, wenn auf einer Ebene mit niedriger Ebene eine Nullzeigerausnahme ausgelöst wird? Warum sollte ich es auf einer höheren Ebene fangen? Für mich ist es nicht sinnvoll, eine Ausnahme auf niedriger Ebene auf einer höheren Ebene zu erfassen, z. B. auf einer Business-Ebene. Es scheint die Bedenken jeder Schicht zu verletzen.

Stellen Sie sich folgende Situation vor:

Ich habe einen Dienst, der eine Zahl berechnet. Um die Zahl zu berechnen, greift der Dienst auf ein Repository zu, um Rohdaten abzurufen, und einige andere Dienste, um die Berechnung vorzubereiten. Wenn auf der Datenabrufebene ein Fehler aufgetreten ist, warum sollte ich eine DataRetrievalException auf eine höhere Ebene setzen? Im Gegensatz dazu würde ich es vorziehen, die Ausnahme in eine sinnvolle Ausnahme zu verpacken, beispielsweise eine CalculationServiceException.

Warum früh werfen, warum spät fangen?

165
shylynx

Nach meiner Erfahrung ist es am besten, Ausnahmen an der Stelle auszulösen, an der die Fehler auftreten. Sie tun dies, weil Sie an diesem Punkt am besten wissen, warum die Ausnahme ausgelöst wurde.

Wenn die Ausnahme die Ebenen wieder abwickelt, ist das Fangen und erneutes Werfen eine gute Möglichkeit, der Ausnahme zusätzlichen Kontext hinzuzufügen. Dies kann bedeuten, dass Sie eine andere Art von Ausnahme auslösen, aber dabei die ursprüngliche Ausnahme einschließen.

Schließlich erreicht die Ausnahme eine Ebene, auf der Sie Entscheidungen über den Codefluss treffen können (z. B. eine Aufforderung an den Benutzer zum Handeln). Dies ist der Punkt, an dem Sie die Ausnahme endgültig behandeln und die normale Ausführung fortsetzen sollten.

Mit Übung und Erfahrung mit Ihrer Codebasis wird es ziemlich einfach zu beurteilen, wann Fehler zusätzlichen Kontext hinzugefügt werden müssen und wo es am sinnvollsten ist, die Fehler tatsächlich endgültig zu behandeln.

Fangen → Neu werfen

Führen Sie dies aus, um weitere Informationen hinzuzufügen, die es einem Entwickler ersparen würden, alle Ebenen durchzuarbeiten, um das Problem zu verstehen.

Fang → Griff

Tun Sie dies, wo Sie endgültige Entscheidungen darüber treffen können, was ein angemessener, aber unterschiedlicher Ausführungsfluss durch die Software ist.

Catch → Error Return

In Situationen, in denen dies angemessen ist, sollten Ausnahmen abgefangen und ein Fehlerwert an den Aufrufer zurückgegeben werden, um eine Implementierung von Catch → Rethrow durchzuführen.

121
Michael Shaw

Sie möchten so schnell wie möglich eine Ausnahme auslösen, da dies das Auffinden der Ursache erleichtert. Stellen Sie sich beispielsweise eine Methode vor, die mit bestimmten Argumenten fehlschlagen könnte. Wenn Sie die Argumente überprüfen und ganz am Anfang der Methode fehlschlagen, wissen Sie sofort, dass der Fehler im aufrufenden Code enthalten ist. Wenn Sie warten, bis die Argumente benötigt werden, bevor Sie fehlschlagen, müssen Sie der Ausführung folgen und herausfinden, ob sich der Fehler im aufrufenden Code befindet (schlechtes Argument) oder ob die Methode einen Fehler aufweist. Je früher Sie die Ausnahme auslösen, desto näher ist sie an der zugrunde liegenden Ursache und desto einfacher ist es herauszufinden, wo etwas schief gelaufen ist.

Der Grund, warum Ausnahmen auf höheren Ebenen behandelt werden, liegt darin, dass die niedrigeren Ebenen nicht wissen, wie der Fehler behandelt werden soll. Tatsächlich kann es je nach aufrufendem Code mehrere geeignete Möglichkeiten geben, denselben Fehler zu behandeln. Nehmen Sie zum Beispiel das Öffnen einer Datei. Wenn Sie versuchen, eine Konfigurationsdatei zu öffnen, die nicht vorhanden ist, kann es eine angemessene Antwort sein, die Ausnahme zu ignorieren und mit der Standardkonfiguration fortzufahren. Wenn Sie eine private Datei öffnen, die für die Ausführung des Programms von entscheidender Bedeutung ist und irgendwie fehlt, besteht Ihre einzige Option wahrscheinlich darin, das Programm zu schließen.

Das Einschließen der Ausnahmen in die richtigen Typen ist ein rein orthogonales Problem.

58
Doval

Andere haben ganz gut zusammengefasst warum früh werfen. Lassen Sie mich stattdessen auf den Teil warum spät dran sein Konzentrieren, für den ich keine zufriedenstellende Erklärung für meinen Geschmack gesehen habe.

WARUM AUSNAHMEN?

Es scheint ziemlich verwirrend zu sein, warum es überhaupt Ausnahmen gibt. Lassen Sie mich hier das große Geheimnis teilen: Der Grund für Ausnahmen und die Ausnahmebehandlung ist ... [~ # ~] Abstraktion [~ # ~] .

Haben Sie Code wie diesen gesehen:

static int divide(int dividend, int divisor) throws DivideByZeroException {
    if (divisor == 0)
        throw new DivideByZeroException(); // that's a checked exception indeed

    return dividend / divisor;
}

static void doDivide() {
    int a = readInt();
    int b = readInt(); 
    try {
        int res = divide(a, b);
        System.out.println(res);
    } catch (DivideByZeroException e) {
        // checked exception... I'm forced to handle it!
        System.out.println("Nah, can't divide by zero. Try again.");
    }
}

So sollten Ausnahmen nicht verwendet werden. Code wie der oben genannte existiert im wirklichen Leben, aber sie sind eher eine Aberration und wirklich die Ausnahme (Wortspiel). Die Definition von Division ist beispielsweise auch in der reinen Mathematik bedingt: Es ist immer der "Aufrufercode", der den Ausnahmefall Null behandeln muss, um die Eingabedomäne einzuschränken. Es ist hässlich. Es ist immer Schmerz für den Anrufer. Für solche Situationen ist das Muster check-then-do der natürliche Weg:

static int divide(int dividend, int divisor) {
    // throws unchecked ArithmeticException for 0 divisor
    return dividend / divisor;
}

static void doDivide() {
    int a = readInt();
    int b = readInt();
    if (b != 0) {
        int res = divide(a, b);
        System.out.println(res);
    } else {
        System.out.println("Nah, can't divide by zero. Try again.");
    }
}

Alternativ können Sie das vollständige Kommando für den Stil OOP) wie folgt ausführen:

static class Division {
    final int dividend;
    final int divisor;

    private Division(int dividend, int divisor) {
        this.dividend = dividend;
        this.divisor = divisor;
    }

    public boolean check() {
        return divisor != 0;
    }

    public int eval() {
        return dividend / divisor;
    }

    public static Division with(int dividend, int divisor) {
        return new Division(dividend, divisor);
    }
}

static void doDivide() {
    int a = readInt();
    int b = readInt(); 
    Division d = Division.with(a, b);
    if (d.check()) {
        int res = d.eval();
        System.out.println(res);
    } else {
        System.out.println("Nah, can't divide by zero. Try again.");
    }
}

Wie Sie sehen, trägt der Anrufercode die Last der Vorabprüfung, führt danach jedoch keine Ausnahmebehandlung durch. Wenn ein ArithmeticException jemals vom Aufrufen von divide oder eval kommt, dann sind es [~ # ~] Sie [~ # ~] wer muss die Ausnahmebehandlung durchführen und Ihren Code reparieren, weil Sie die check() vergessen haben. Aus den ähnlichen Gründen ist es fast immer falsch, ein NullPointerException zu fangen.

Nun gibt es einige Leute, die sagen, sie möchten die Ausnahmefälle in der Methoden-/Funktionssignatur sehen, d. H. Die Ausgabedomäne explizit erweitern . Sie sind diejenigen, die geprüfte Ausnahmen bevorzugen. Natürlich sollte das Ändern der Ausgabedomäne die Anpassung jedes direkten Anrufercodes erzwingen, und dies würde in der Tat mit aktivierten Ausnahmen erreicht werden. Dafür brauchen Sie aber keine Ausnahmen! Deshalb haben Sie Nullable<T>generische Klassen , Fallklassen , algebraische Datentypen und Vereinigungstypen . Einige OO Personen ) ziehen es möglicherweise sogar vor,null für einfache Fehlerfälle zurückzugeben so was:

static Integer divide(int dividend, int divisor) {
    if (divisor == 0) return null;
    return dividend / divisor;
}

static void doDivide() {
    int a = readInt();
    int b = readInt(); 
    Integer res = divide(a, b);
    if (res != null) {
        System.out.println(res);
    } else {
        System.out.println("Nah, can't divide by zero. Try again.");
    }
}

Technisch gesehen können Ausnahmen können für den oben genannten Zweck verwendet werden, aber hier ist der Punkt: Ausnahmen existieren für eine solche Verwendung nicht . Ausnahmen sind Pro-Abstraktion. Ausnahmen sind Indirektion. Ausnahmen ermöglichen es, die "Ergebnis" -Domäne zu erweitern, ohne direct Client-Verträge zu unterbrechen, und die Fehlerbehandlung auf "irgendwo anders" zu verschieben. Wenn Ihr Code Ausnahmen auslöst, die in direkten Aufrufern desselben Codes ohne dazwischen liegende Abstraktionsebenen behandelt werden, dann tun Sie dies W. R. O. N. G.

WIE SPÄT FANGEN?

So hier sind wir. Ich habe mich durchgesetzt, um zu zeigen, dass die Verwendung von Ausnahmen in den obigen Szenarien nicht die Verwendung von Ausnahmen ist. Es gibt jedoch einen echten Anwendungsfall, bei dem die Abstraktion und Indirektion, die die Ausnahmebehandlung bietet, unverzichtbar ist. Das Verständnis einer solchen Verwendung hilft auch beim Verständnis der Empfehlung spät dran.

Dieser Anwendungsfall ist: Programmieren gegen Ressourcenabstraktionen ...

Ja, Geschäftslogik sollte gegen Abstraktionen programmiert werden, nicht gegen konkrete Implementierungen. Der Code der obersten Ebene [~ # ~] ioc [~ # ~] "Verkabelung" instanziiert die konkreten Implementierungen der Ressourcenabstraktionen und gibt sie an die Geschäftslogik weiter. Hier gibt es nichts Neues. Aber die konkreten Implementierungen dieser Ressourcenabstraktionen können möglicherweise ihre eigenen implementierungsspezifischen Ausnahmen auslösen, nicht wahr?

Wer kann mit diesen implementierungsspezifischen Ausnahmen umgehen? Ist es dann überhaupt möglich, ressourcenspezifische Ausnahmen in der Geschäftslogik zu behandeln? Nein, das ist es nicht. Die Geschäftslogik ist gegen Abstraktionen programmiert, wodurch die Kenntnis dieser implementierungsspezifischen Ausnahmedetails ausgeschlossen wird.

"Aha!", Könnte man sagen: "Aber deshalb können wir Ausnahmen unterordnen und Ausnahmehierarchien erstellen" (siehe Mr. Spring !). Lass mich dir sagen, das ist ein Trugschluss. Erstens sagt jedes vernünftige Buch über OOP, dass konkrete Vererbung schlecht ist, aber irgendwie ist diese Kernkomponente von JVM, die Ausnahmebehandlung, eng mit konkreter Vererbung verbunden. Ironischerweise hätte Joshua Bloch seine nicht schreiben können Effektiv Java Buch , bevor er die Erfahrung mit einer funktionierenden JVM sammeln konnte, oder? Es ist eher ein "Lessons Learned" -Buch für das nächste Generation. Zweitens, und was noch wichtiger ist, wenn Sie eine Ausnahme auf hoher Ebene haben, wie werden Sie sie dann behandeln? PatientNeedsImmediateAttentionException: Müssen wir ihr eine Pille geben oder ihre Beine amputieren? Wie wäre es mit einem Schalter? Aussage über alle möglichen Unterklassen? Da geht Ihr Polymorphismus, da geht die Abstraktion. Sie haben den Punkt.

Wer kann also mit den ressourcenspezifischen Ausnahmen umgehen? Es muss derjenige sein, der die Konkretionen kennt! Derjenige, der die Ressource instanziiert hat! Der "Verkabelungs" -Code natürlich! Überprüfen Sie dies heraus:

Geschäftslogik gegen Abstraktionen codiert ... KEINE BETONRESSOURCENFEHLERBEHANDLUNG!

static interface InputResource {
    String fetchData();
}

static interface OutputResource {
    void writeData(String data);
}

static void doMyBusiness(InputResource in, OutputResource out, int times) {
    for (int i = 0; i < times; i++) {
        System.out.println("fetching data");
        String data = in.fetchData();
        System.out.println("outputting data");
        out.writeData(data);
    }
}

Inzwischen woanders die konkreten Implementierungen ...

static class ConstantInputResource implements InputResource {
    @Override
    public String fetchData() {
        return "Hello World!";
    }
}

static class FailingInputResourceException extends RuntimeException {
    public FailingInputResourceException(String message) {
        super(message);
    }
}

static class FailingInputResource implements InputResource {
    @Override
    public String fetchData() {
        throw new FailingInputResourceException("I am a complete failure!");
    }
}

static class StandardOutputResource implements OutputResource {
    @Override
    public void writeData(String data) {
        System.out.println("DATA: " + data);
    }
}

Und schließlich der Verdrahtungscode ... Wer behandelt konkrete Ressourcenausnahmen? Derjenige, der über sie Bescheid weiß!

static void start() {
    InputResource in1 = new FailingInputResource();
    InputResource in2 = new ConstantInputResource();
    OutputResource out = new StandardOutputResource();

    try {
        ReusableBusinessLogicClass.doMyBusiness(in1, out, 3);
    }
    catch (FailingInputResourceException e)
    {
        System.out.println(e.getMessage());
        System.out.println("retrying...");
        ReusableBusinessLogicClass.doMyBusiness(in2, out, 3);
    }
}

Jetzt trage mit mir. Der obige Code ist simpel. Sie könnten sagen, Sie haben eine Unternehmensanwendung/einen Webcontainer mit mehreren Bereichen von IOC Container-verwalteten Ressourcen, und Sie benötigen automatische Wiederholungsversuche und Neuinitialisierung von Ressourcen für Sitzungs- oder Anforderungsbereiche usw. Die Verkabelungslogik auf dem Bereiche auf niedrigerer Ebene erhalten möglicherweise abstrakte Fabriken, um Ressourcen zu erstellen, und sind sich daher der genauen Implementierungen nicht bewusst. Nur Bereiche auf höherer Ebene würden wirklich wissen, welche Ausnahmen diese Ressourcen auf niedrigerer Ebene auslösen können.

Leider erlauben Ausnahmen nur die Indirektion über den Aufrufstapel, und verschiedene Bereiche mit ihren unterschiedlichen Kardinalitäten werden normalerweise auf mehreren verschiedenen Threads ausgeführt. Keine Möglichkeit, mit Ausnahmen darüber zu kommunizieren. Wir brauchen hier etwas Mächtigeres. Antwort: asynchrone Nachrichtenübergabe . Fangen Sie jede Ausnahme an der Wurzel des Bereichs der unteren Ebene ab. Ignoriere nichts, lass nichts durch. Dadurch werden alle auf dem Aufrufstapel des aktuellen Bereichs erstellten Ressourcen geschlossen und entsorgt. Verbreiten Sie dann Fehlermeldungen in höheren Bereichen, indem Sie Nachrichtenwarteschlangen/-kanäle in der Ausnahmebehandlungsroutine verwenden, bis Sie die Ebene erreichen, auf der die Konkretionen bekannt sind. Das ist der Typ, der weiß, wie man damit umgeht.

SUMMA SUMMARUM

Nach meiner Interpretation bedeutet spät fangen Ausnahmen am bequemsten Ort zu fangen , WO SIE NICHT MEHR ABSTRAKTION BRECHEN . Nicht zu früh fangen! Fangen Sie Ausnahmen auf der Ebene ab, auf der Sie die konkrete Ausnahme erstellen, die Instanzen der Ressourcenabstraktionen auslöst, die Ebene, die weiß die Konkretionen der Abstraktionen. Die "Verdrahtungs" -Schicht.

HTH. Viel Spaß beim Codieren!

24
Daniel Dinnyes

Um diese Frage richtig zu beantworten, treten wir einen Schritt zurück und stellen eine noch grundlegendere Frage.

Warum haben wir überhaupt Ausnahmen?

Wir lösen Ausnahmen aus, um den Aufrufer unserer Methode darüber zu informieren, dass wir nicht das tun konnten, worum wir gebeten wurden. Der Typ der Ausnahme erklärt warum wir konnten nicht tun, was wir wollten.

Schauen wir uns einen Code an:

double MethodA()
{
    return PropertyA - PropertyB.NestedProperty;
}

Dieser Code kann offensichtlich eine Nullreferenzausnahme auslösen, wenn PropertyB null ist. Es gibt zwei Dinge, die wir in diesem Fall tun könnten, um diese Situation zu "korrigieren". Wir können:

  • Erstellen Sie PropertyB automatisch, wenn wir es nicht haben. oder
  • Lassen Sie die Ausnahme bis zur aufrufenden Methode aufsteigen.

Das Erstellen von PropertyB hier kann sehr gefährlich sein. Welchen Grund hat diese Methode, um PropertyB zu erstellen? Dies würde sicherlich gegen das Prinzip der Einzelverantwortung verstoßen. Wenn PropertyB hier nicht vorhanden ist, weist dies aller Wahrscheinlichkeit nach darauf hin, dass ein Fehler aufgetreten ist. Die Methode wird für ein teilweise erstelltes Objekt aufgerufen oder PropertyB wurde fälschlicherweise auf null gesetzt. Wenn Sie hier PropertyB erstellen, können wir einen viel größeren Fehler verbergen, der uns später beißen könnte, z. B. einen Fehler, der zu Datenkorruption führt.

Wenn wir stattdessen die Nullreferenz aufblasen lassen, teilen wir dem Entwickler, der diese Methode aufgerufen hat, so schnell wie möglich mit, dass ein Fehler aufgetreten ist. Eine wichtige Voraussetzung für den Aufruf dieser Methode wurde übersehen.

Tatsächlich werfen wir also früh, weil dies unsere Bedenken viel besser voneinander trennt. Sobald ein Fehler aufgetreten ist, informieren wir die vorgelagerten Entwickler darüber.

Warum wir "spät dran sind", ist eine andere Geschichte. Wir wollen nicht wirklich spät fangen, wir wollen wirklich so früh fangen, wie wir wissen, wie wir mit dem Problem richtig umgehen sollen. Manchmal werden dies später fünfzehn Abstraktionsebenen sein, und manchmal wird dies der Punkt der Schöpfung sein.

Der Punkt ist, dass wir die Ausnahme auf der Abstraktionsebene abfangen möchten, die es uns ermöglicht, die Ausnahme an dem Punkt zu behandeln, an dem wir alle Informationen haben, die wir benötigen, um die Ausnahme richtig zu behandeln.

10
Stephen

Werfen Sie, sobald Sie etwas sehen, für das es sich zu werfen lohnt, um zu vermeiden, dass Objekte in einen ungültigen Zustand versetzt werden. Das heißt, wenn ein Nullzeiger übergeben wurde, prüfen Sie ihn frühzeitig und werfen einen NPE vor, der die Chance hat, auf den niedrigen Pegel zu rinnen.

Fangen Sie ab, sobald Sie wissen, was zu tun ist, um den Fehler zu beheben (dies ist im Allgemeinen nicht der Punkt, an dem Sie werfen, sonst könnten Sie einfach ein if-else verwenden). Wenn ein ungültiger Parameter übergeben wurde, sollte die Ebene, die den Parameter bereitgestellt hat, die Konsequenzen behandeln .

6
ratchet freak

Eine gültige Geschäftsregel lautet: "Wenn die Software der unteren Ebene keinen Wert berechnet, dann ..."

Dies kann nur auf der höheren Ebene ausgedrückt werden, andernfalls versucht die Software auf der niedrigeren Ebene, ihr Verhalten basierend auf ihrer eigenen Korrektheit zu ändern, was nur mit einem Knoten enden wird.

4
soru

Ausnahmen gelten zunächst für Ausnahmesituationen. In Ihrem Beispiel kann keine Zahl berechnet werden, wenn die Rohdaten nicht vorhanden sind, da sie nicht geladen werden konnten.

Nach meiner Erfahrung ist es empfehlenswert, Ausnahmen zu abstrahieren, während Sie den Stapel hinaufgehen. In der Regel möchten Sie dies tun, wenn eine Ausnahme die Grenze zwischen zwei Ebenen überschreitet.

Wenn beim Sammeln Ihrer Rohdaten in der Datenschicht ein Fehler auftritt, lösen Sie eine Ausnahme aus, um zu benachrichtigen, wer die Daten angefordert hat. Versuchen Sie nicht, dieses Problem hier unten zu umgehen. Die Komplexität des Handhabungscodes kann sehr hoch sein. Außerdem ist die Datenschicht nur für die Anforderung von Daten verantwortlich, nicht für die Behandlung von Fehlern, die dabei auftreten. Dies ist gemeint mit "früh werfen".

In Ihrem Beispiel ist die Fangschicht die Serviceschicht. Der Dienst selbst ist eine neue Schicht, die über der Datenzugriffsschicht liegt. Sie möchten dort also die Ausnahme abfangen. Möglicherweise verfügt Ihr Dienst über eine Failover-Infrastruktur und versucht, die Daten von einem anderen Repository anzufordern. Wenn dies ebenfalls fehlschlägt, schließen Sie die Ausnahme in etwas ein, das der Aufrufer des Dienstes versteht (wenn es sich um einen Webdienst handelt, kann dies ein SOAP Fehler) sein. Legen Sie die ursprüngliche Ausnahme als innere Ausnahme fest, damit die spätere Ebenen können genau protokollieren, was schief gelaufen ist.

Der Dienstfehler kann von der Schicht abgefangen werden, die den Dienst aufruft (z. B. die Benutzeroberfläche). Und das ist es, was mit "spät fangen" gemeint ist. Wenn Sie die Ausnahme in einer unteren Ebene nicht behandeln können, werfen Sie sie erneut. Wenn die oberste Ebene die Ausnahme nicht behandeln kann, behandeln Sie sie! Dies kann das Protokollieren oder Präsentieren umfassen.

Der Grund, warum Sie Ausnahmen erneut auslösen sollten (wie oben beschrieben, indem Sie sie in allgemeinere Ausnahmen einschließen), besteht darin, dass der Benutzer sehr wahrscheinlich nicht verstehen kann, dass ein Fehler aufgetreten ist, weil beispielsweise ein Zeiger auf einen ungültigen Speicher verweist. Und es ist ihm egal. Er kümmert sich nur darum, dass die Zahl vom Dienst nicht berechnet werden konnte und dies sind die Informationen, die ihm angezeigt werden sollten.

Wenn Sie weiter gehen, können Sie (in einer idealen Welt) try/catch Code vollständig von der Benutzeroberfläche weglassen. Verwenden Sie stattdessen einen globalen Ausnahmebehandler, der die Ausnahmen verstehen kann, die möglicherweise von den unteren Ebenen ausgelöst werden, sie in ein Protokoll schreibt und in Fehlerobjekte einschließt, die aussagekräftige (und möglicherweise lokalisierte) Informationen zum Fehler enthalten. Diese Objekte können dem Benutzer problemlos in jeder gewünschten Form präsentiert werden (Nachrichtenfelder, Benachrichtigungen, Nachrichtentoasts usw.).

2
Aschratt

Das frühzeitige Auslösen von Ausnahmen ist im Allgemeinen eine gute Vorgehensweise, da Sie nicht möchten, dass fehlerhafte Verträge weiter als erforderlich durch den Code fließen. Wenn Sie beispielsweise erwarten, dass ein bestimmter Funktionsparameter eine positive Ganzzahl ist, sollten Sie diese Einschränkung am Punkt des Funktionsaufrufs erzwingen, anstatt zu warten, bis diese Variable an einer anderen Stelle im Codestapel verwendet wird.

Ich kann mich nicht wirklich dazu äußern, weil ich meine eigenen Regeln habe und diese sich von Projekt zu Projekt ändern. Ich versuche jedoch, die Ausnahmen in zwei Gruppen zu unterteilen. Einer ist nur für den internen Gebrauch und der andere nur für den externen Gebrauch. Interne Ausnahmen werden von meinem eigenen Code abgefangen und behandelt, und externe Ausnahmen sollen von jedem Code behandelt werden, der mich aufruft. Dies ist im Grunde eine Form, Dinge später zu fangen, aber nicht ganz, weil es mir die Flexibilität bietet, bei Bedarf im internen Code von der Regel abzuweichen.

1
davidk01