Andere haben recht gut zusammengefasst, warum man früh wirft . Lassen Sie mich stattdessen auf das Warum konzentrieren, um den späten Teil zu fangen , für den ich keine befriedigende Erklärung für meinen Geschmack gesehen habe.
WARUM AUSNAHMEN?
Es scheint ziemlich verwirrend zu sein, warum es überhaupt Ausnahmen gibt. Lassen Sie mich das große Geheimnis hier teilen: Der Grund für Ausnahmen und die Behandlung von Ausnahmen 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 obige gibt es im wirklichen Leben, aber sie sind eher eine Aberration und sind wirklich die Ausnahme (Wortspiel). Die Definition der Division ist beispielsweise auch in der reinen Mathematik bedingt: Es ist immer der "Aufrufercode", der den Ausnahmefall Null behandeln muss, um den Eingabebereich einzuschränken. Es ist hässlich. Es tut dem Anrufer immer weh. Dennoch ist in solchen Situationen das Check-Then-Do- Muster 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 den OOP-Stil wie folgt steuern:
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, hat der Anrufercode die Last der Vorabprüfung, führt jedoch keine Ausnahmebehandlung mehr durch. Kommt ArithmeticException
jemals ein Aufruf von divide
oder eval
, dann bist DU es, der die Ausnahmebehandlung durchführen und deinen Code korrigieren muss, weil du den vergessen hast check()
. Aus den gleichen Gründen NullPointerException
ist es fast immer falsch, eine zu fangen .
Nun gibt es einige Leute , die sagen , dass sie die Ausnahmefälle in der Methode / Funktion Unterschrift sehen wollen, also explizit die Ausgabe zu erweitern Domäne . Sie sind diejenigen, die geprüfte Ausnahmen bevorzugen . Natürlich sollte das Ändern der Ausgabedomäne eine Anpassung des Codes eines direkten Anrufers erzwingen, und dies würde in der Tat mit überprüften Ausnahmen erreicht. Aber dafür braucht man keine Ausnahmen! Aus diesem Grund gibt es Nullable<T>
generische Klassen , Fallklassen , algebraische Datentypen und Vereinigungstypen . Einige OO-Leute ziehen es vielleicht sogar vor, null
für einfache Fehlerfälle wie diesen zurückzukehren:
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 Ausnahmen können für Zwecke wie die oben verwendet werden, aber hier ist der Punkt: Ausnahmen existieren nicht für eine solche Nutzung . Ausnahmen sind pro Abstraktion. Ausnahme sind etwa Indirektionen. Ausnahmen ermöglichen es, die "Ergebnis" -Domäne zu erweitern, ohne direkte Kundenverträge zu brechen , und die Fehlerbehandlung auf "irgendwo anders" zu verschieben. Wenn Ihr Code Ausnahmen auslöst , die von Direktaufrufern desselben Codes behandelt werden, ohne dazwischen liegende Abstraktionsebenen, dann tun Sie dies FALSCH
WIE SPÄTEN?
So hier sind wir. Ich habe mich durchgearbeitet, um zu zeigen, dass die Verwendung von Ausnahmen in den obigen Szenarien nicht die Verwendung von Ausnahmen bedeutet. Es gibt jedoch einen echten Anwendungsfall, bei dem die durch die Ausnahmebehandlung gebotene Abstraktion und Indirektion unverzichtbar ist. Das Verstehen einer solchen Verwendung hilft auch dabei, die Empfehlung für einen späten Fang zu verstehen .
Dieser Anwendungsfall lautet: Programmieren gegen Ressourcenabstraktionen ...
Ja, Geschäftslogik sollte gegen Abstraktionen programmiert werden , nicht gegen konkrete Implementierungen. Übergeordneter IOC- Verkabelungscode instanziiert die konkreten Implementierungen der Ressourcenabstraktionen und leitet sie an die Geschäftslogik weiter. Hier gibt es nichts Neues. Die konkreten Implementierungen dieser Ressourcenabstraktionen können jedoch möglicherweise ihre eigenen implementierungsspezifischen Ausnahmen auslösen , nicht wahr?
Wer kann mit diesen implementierungsspezifischen Ausnahmen umgehen? Ist es dann möglich, irgendwelche ressourcenspezifischen Ausnahmen in der Geschäftslogik zu behandeln? Nein, das ist es nicht. Die Geschäftslogik ist gegen Abstraktionen programmiert, was die Kenntnis dieser implementierungsspezifischen Ausnahmedetails ausschließt.
"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 sein Effective Java-Buch nicht schreiben können, bevor er die Erfahrung mit einer funktionierenden JVM sammeln konnte, oder? Es ist eher ein Lehrbuch für die nächste Generation. Zweitens, und was noch wichtiger ist, wenn Sie eine Ausnahme auf hoher Ebene feststellen, wie gehen Sie dann damit um?PatientNeedsImmediateAttentionException
: Müssen wir ihr eine Pille geben oder ihre Beine amputieren? Wie wäre es mit einer switch-Anweisung über alle möglichen Unterklassen? Da ist dein Polymorphismus, da ist die Abstraktion. Du hast es erfasst.
Wer kann 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, die gegen Abstraktionen codiert ist ... KEINE BEHANDLUNG VON BETONRESSOURCENFEHLERN!
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 Umsetzungen ...
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 Verkabelungscode ... 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 ertrage mit mit mir. Der obige Code ist einfach. Sie könnten sagen, Sie haben eine Unternehmensanwendung / einen Webcontainer mit mehreren Bereichen für von IOC-Containern verwaltete Ressourcen, und Sie benötigen automatische Wiederholungsversuche und Neuinitialisierung von Ressourcen für Sitzungs- oder Anforderungsbereiche usw. Der Verkabelungslogik auf den Bereichen der unteren Ebene können abstrakte Fabriken zugewiesen werden Erstellen Sie Ressourcen, ohne die genauen Implementierungen zu kennen. Nur übergeordnete Bereiche würden wirklich wissen, welche Ausnahmen diese untergeordneten Ressourcen auslösen können. Jetzt warte!
Leider erlauben Ausnahmen nur die Indirektion über den Aufrufstapel, und verschiedene Bereiche mit ihren unterschiedlichen Kardinalitäten werden normalerweise in mehreren verschiedenen Threads ausgeführt. Keine Möglichkeit, damit mit Ausnahmen zu kommunizieren. Wir brauchen hier etwas Mächtigeres. Antwort: asynchrone Nachrichtenübermittlung . Fangen Sie jede Ausnahme an der Wurzel des Bereichs der unteren Ebene ab. Ignoriere nichts, lass nichts durch. Dadurch werden alle im Aufrufstapel des aktuellen Bereichs erstellten Ressourcen geschlossen und freigegeben. Geben Sie dann Fehlermeldungen an Bereiche weiter oben weiter, 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" also, Ausnahmen an der günstigsten Stelle zu fangen, an der Sie die ABSTRAKTION NICHT MEHR UNTERBRECHEN . Fang nicht zu früh! Fangen Sie Ausnahmen auf der Ebene ab, auf der Sie die konkrete Ausnahme erstellen, die Instanzen der Ressourcenabstraktionen auslöst. Diese Ebene kennt die Konkretionen der Abstraktionen. Die "Verdrahtungs" -Schicht.
HTH. Viel Spaß beim Codieren!