Eine Methode liefert einen speziellen Wert, um einen Fehler zu melden.
Lửsen Sie statt dessen eine Ausnahme aus.
int withdraw(int amount) { if (amount > _balance) return -1;
else {
_balance -= amount;
return 0;
} }
void withdraw(int amount) throws BalanceException { if (amount > _balance) throw new BalanceException();
_balance -= amount;
}
➾
10.14.1 Motivation
In Computern geht, wie im richtigen Leben, ab und zu etwas schief. Wenn etwas schief geht, mỹssen Sie sich darum kỹmmern. Im einfachsten Fall kửnnen Sie das Programm mit einem Fehler beenden. Dies ist das Softwaregegenstück dazu, Selbstmord zu begehen, weil Sie einen Flug verpasst haben. (Wenn ich das tun wỹrde, wọre ich nicht mehr am Leben, selbst wenn ich eine Katze wọre.) Obwohl dies etwas humorvoll dahergesagt ist, hat die Software-Selbstmordoption ihren Sinn. Wenn die Kosten eines Programmabbruchs niedrig sind und der Anwender tolerant ist, so ist es in Ordnung, das Programm einfach abzubrechen. Wichtigere Programme erfordern aber gewichtigere Maònahmen.
Das Problem besteht darin, dass der Teil des Programms, der einen Fehler ent- deckt, nicht immer der ist, der herausfinden kann, was nun zu tun ist. Wenn eine solche Routine einen Fehler findet, muss sie es den Aufrufer wissen lassen, und der Aufrufer kann den Fehler in der Aufrufhierarchie weiterreichen. In vielen Sprachen wird eine spezielle Ausgabe verwendet, um einen Fehler zu melden.
Unix- und C-basierte Systeme verwenden traditionellerweise einen Returncode, um Erfolg oder Misserfolg einer Routine zu melden.
Java hat eine bessere Mửglichkeit: Ausnahmen. Ausnahmen sind besser, weil sie die normale Verarbeitung klar von der Fehlerbehandlung trennen. Das macht Programme leichter verstọndlich, und ich hoffe, Sie glauben mir inzwischen, dass Verstọndlichkeit das Beste ist, was Sie erreichen kửnnen, wenn Sie schon nicht vollkommen sein kửnnen.
10.14.2 Vorgehen
• Entscheiden Sie, ob die Ausnahme überwacht werden soll oder nicht.
Wenn der Aufrufer dafür verantwortlich ist, die Bedingung vor dem Aufruf zu prü- fen, überwachen Sie die Ausnahme nicht.
Wenn die Ausnahme überwacht wird, erstellen Sie eine neue oder verwenden eine existierende.
• Suchen Sie alle Aufrufer, und lassen Sie sie die Ausnahme verwenden.
Wenn die Ausnahme nicht überwacht wird, lassen Sie die Aufrufer die Prüfung vornehmen. Wandeln Sie nach jeder Änderung um und testen Sie.
Wird die Ausnahme abgefangen, lassen Sie die Aufrufer die Methode in einem try-Block aufrufen.
• Ändern Sie die Signatur der Methode entsprechend der neuen Verwendung.
➾
➾
➾
➾
Wenn Sie viele Aufrufer haben, kann diese Änderung zu umfangreich sein. Sie kửnnen sie mit den folgenden Schritten allmọhlich durchfỹhren:
• Entscheiden Sie, ob die Ausnahme abgefangen werden soll oder nicht.
• Erstellen Sie eine neue Methode, die die Ausnahme verwendet.
• Lassen Sie die alte Methode die neue aufrufen.
• Wandeln Sie um und testen Sie.
• Lassen Sie die Aufrufer der alten Methode die neue benutzen. Wandeln Sie nach jeder Änderung um und testen Sie.
• Lửschen Sie die alte Methode.
10.14.3 Beispiel
Ist es nicht sonderbar, dass Lehrbỹcher immer unterstellen, Sie kửnnten nicht mehr als Ihr Guthaben von Ihrem Konto abheben, obwohl Sie dies im wirklichen Leben oft kửnnen?
class Account...
int withdraw(int amount) { if (amount > _balance) return -1;
else {
_balance -= amount;
return 0;
} }
private int _balance;
Um diesen Code so zu ọndern, dass er eine Ausnahme verwendet, muss ich zu- nọchst entscheiden, ob ich eine ỹberwachte Ausnahme verwenden will oder nicht. Die Entscheidung họngt davon ab, ob es in der Verantwortung des Clients liegt, das Guthaben zu prüfen, oder ob dies in der Verantwortung der Routine liegt. Liegt es in der Verantwortung des Clients, das Guthaben zu prüfen, so ist es ein Programmierfehler, withdraw mit einem Betrag (amount) aufzurufen, der grửòer ist als das Guthaben (_balance). Da es ein Programmierfehler ist – das heiòt ein vom Programmierer verursachter Fehler – sollte ich eine nicht überwachte Aus- nahme verwenden. Liegt das Prüfen des Guthabens in der Verantwortung der withdraw-Routine, so muss ich die Ausnahme in der Schnittstelle deklarieren. So zeige ich dem Aufrufer, welche Ausnahme zu erwarten ist und veranlasse ihn, sie angemessen zu behandeln.
10.14.4 Beispiel: Nicht überwachte Ausnahme
Lassen Sie uns zunọchst den nicht ỹberwachten Fall behandeln. Hier erwarte ich, dass der Aufrufer die Prüfung vornimmt. In diesem Fall sollte niemand den Re- turncode verwenden, da es sich um einen Programmierfehler handelt, dies zu tun. Wenn ich Code sehe wie:
if (account.withdraw(amount) == -1) handleOverdrawn();
else doTheUsualThing();
muss ich diesen durch Code wie
if (!account.canWithdraw(amount)) handleOverdrawn();
else {
account.withdraw(amount);
doTheUsualThing();
}
ersetzen. Nach jeder Änderung kann ich umwandeln und testen.
Nun muss ich den Returncode entfernen und im Fehlerfall eine Ausnahme auslử- sen. Da dieses Verhalten (nach Definition) eine Ausnahme ist, sollte ich eine Wọchterbedingung fỹr das Prỹfen der Bedingung verwenden:
void withdraw(int amount) { if (amount > _balance)
throw new IllegalArgumentException ("Amount too large");
_balance -= amount;
}
Das es sich um einen Programmierfehler handelt, sollte ich dies noch deutlicher herausstreichen, indem ich eine Zusicherung verwende:
class Account...
void withdraw(int amount) {
Assert.isTrue ("amount too large", amount > _balance);
_balance -= amount;
}
class Assert...
static void isTrue (String comment, boolean test) { if (! test) {
throw new RuntimeException ("Assertion failed: " + comment);
} }
10.14.5 Beispiel: ĩberwachte Ausnahme
Ich behandle den Fall der überwachten Ausnahme etwas anders. Als Erstes erstelle (oder verwende) ich die geeignete neue Ausnahme:
class BalanceException extends Exception {}
Dann lasse ich die Aufrufer diese verwenden:
try {
account.withdraw(amount);
doTheUsualThing();
} catch (BalanceException e) { handleOverdrawn();
}
Nun lasse ich auch die Methode withdraw die neue Ausnahme verwenden:
void withdraw(int amount) throws BalanceException { if (amount > _balance) throw new BalanceException();
_balance -= amount;
}
Das Stửrende an dieser Prozedur ist, dass ich alle Aufrufer und die aufgerufene Routine in einem Durchgang ọndern muss. Andernfalls gibt es einen Klaps vom Compiler. Gibt es viele Aufrufer, so ist dieser Schritt eine zu groòe Änderung ohne den Schritt des Umwandelns und Testens.
In solchen Fọllen kann ich eine temporọre ĩbergangsmethode verwenden. Ich beginne mit dem gleichen Fall wie vorher:
if (account.withdraw(amount) == -1) handleOverdrawn();
else doTheUsualThing();
class Account ...
int withdraw(int amount) { if (amount > _balance) return -1;
else {
_balance -= amount;
return 0;
} }
Im ersten Schritt erstelle ich eine neue withdraw-Methode, die die Ausnahme ver- wendet:
void newWithdraw(int amount) throws BalanceException { if (amount > _balance) throw new BalanceException();
_balance -= amount;
}
Als Nọchstes lasse ich die aktuelle withdraw-Methode die neue Methode verwen- den:
int withdraw(int amount) { try {
newWithdraw(amount);
return 0;
} catch (BalanceException e) { return -1;
} }
Nachdem das erledigt ist, kann ich umwandeln und testen. Nun kann ich alle Aufrufe der alten Methode durch Aufrufe der neuen ersetzen:
try {
account.newWithdraw(amount);
doTheUsualThing();
} catch (BalanceException e) { handleOverdrawn();
}
Mit der fertigen alten und der neuen Methode kann ich nach jeder Änderung um- wandeln und testen. Wenn ich fertig bin, kann ich die alte Methode lửschen und Methode umbenennen (279) anwenden, um der neuen Methode den Namen der al- ten zu geben.