Ausnahmen
Das Wort “Ausnahme” wird im gleichen Sinne verwendet wie der Ausdruck “Ich nehme Anstoß daran.”
Eine außergewöhnliche Bedingung verhindert die Fortsetzung der aktuellen Funktion oder des aktuellen Bereichs. An dem Punkt, an dem das Problem auftritt, wissen Sie möglicherweise nicht, was Sie damit tun sollen, aber Sie können im aktuellen Kontext nicht fortfahren. Sie haben nicht genügend Informationen, um das Problem zu beheben. Daher müssen Sie stoppen und das Problem an einen anderen Kontext übergeben, der geeignete Maßnahmen ergreifen kann.
Dieses Atom behandelt die Grundlagen von Ausnahmen als ein Mechanismus zur Fehlerberichterstattung. In Abschnitt VI: Fehlervermeidung betrachten wir andere Möglichkeiten, mit Problemen umzugehen.
Es ist wichtig, eine außergewöhnliche Bedingung von einem normalen Problem zu unterscheiden. Ein normales Problem verfügt über genügend Informationen im aktuellen Kontext, um das Problem zu bewältigen. Bei einer außergewöhnlichen Bedingung können Sie die Verarbeitung nicht fortsetzen. Alles, was Sie tun können, ist zu gehen und das Problem einem externen Kontext zu überlassen. Dies ist der Fall, wenn Sie eine Ausnahme werfen. Die Ausnahme ist das Objekt, das vom Ort des Fehlers “geworfen” wird.
Betrachten Sie toInt(), das einen String in einen Int umwandelt. Was passiert, wenn Sie diese Funktion für einen String aufrufen, der keinen ganzzahligen Wert enthält?
// Exceptions/ToIntException.kt
package exceptions
fun erroneousCode() {
// Uncomment this line to get an exception:
// val i = "1$".toInt() // [1]
}
fun main() {
erroneousCode()
}
Das Auskommentieren der Zeile [1] führt zu einer Ausnahme. Hier ist die fehlerhafte Zeile kommentiert, damit der Aufbau des Buches nicht gestoppt wird, das überprüft, ob jedes Beispiel wie erwartet kompiliert und ausgeführt wird.
Wenn eine Ausnahme ausgelöst wird, stoppt der Ausführungspfad—derjenige, der nicht fortgesetzt werden kann—und das Ausnahmeobjekt wird aus dem aktuellen Kontext herausgeworfen. Hier verlässt es den Kontext von erroneousCode() und geht in den Kontext von main(). In diesem Fall meldet Kotlin nur den Fehler; der Programmierer hat vermutlich einen Fehler gemacht und muss den Code korrigieren.
Wenn eine Ausnahme nicht abgefangen wird, bricht das Programm ab und zeigt einen Stack-Trace mit detaillierten Informationen an. Das Auskommentieren der Zeile [1] in ToIntException.kt führt zu folgendem Output:
Exception in thread "main" java.lang.NumberFormatException: For input s\
tring: "1$"
at java.lang.NumberFormatException.forInputString(NumberFormatExcepti\
on.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at ToIntExceptionKt.erroneousCode(at ToIntException.kt:6)
at ToIntExceptionKt.main(at ToIntException.kt:10)
Der Stack-Trace liefert Details wie die Datei und die Zeile, in der die Ausnahme aufgetreten ist, sodass Sie das Problem schnell entdecken können. Die letzten beiden Zeilen zeigen das Problem: In Zeile 10 von main() rufen wir erroneousCode() auf. Dann, genauer gesagt, in Zeile 6 von erroneousCode() rufen wir toInt() auf.
Um das Kommentieren und Auskommentieren von Code zur Anzeige von Ausnahmen zu vermeiden, verwenden wir die Funktion capture() aus dem Paket AtomicTest:
// Exceptions/IntroducingCapture.kt
import atomictest.*
fun main() {
capture {
"1$".toInt()
} eq "NumberFormatException: " +
"""For input string: "1$""""
}
Mit capture() vergleichen wir die generierte Ausnahme mit der erwarteten Fehlermeldung. capture() ist nicht sehr hilfreich für normale Programmierung - es ist speziell für dieses Buch entworfen, damit Sie die Ausnahme sehen und wissen können, dass die Ausgabe vom Build-System des Buches überprüft wurde.
Eine weitere Strategie, wenn Sie das erwartete Ergebnis nicht erfolgreich erzielen können, besteht darin, null zurückzugeben, eine spezielle Konstante, die “kein Wert” bedeutet. Sie können null anstelle eines Wertes jeden Typs zurückgeben. Später in Nullable Typen besprechen wir, wie null den Typ des resultierenden Ausdrucks beeinflusst.
Die Kotlin-Standardbibliothek enthält String.toIntOrNull(), das die Umwandlung durchführt, wenn der String eine ganze Zahl enthält, oder null produziert, wenn die Umwandlung unmöglich ist - null ist eine einfache Möglichkeit, einen Fehler anzuzeigen:
// Exceptions/IntroducingNull.kt
import atomictest.eq
fun main() {
"1$".toIntOrNull() eq null
}
Angenommen, wir berechnen das durchschnittliche Einkommen über einen Zeitraum von Monaten:
// Exceptions/AverageIncome.kt
package firstversion
import atomictest.*
fun averageIncome(income: Int, months: Int) =
income / months
fun main() {
averageIncome(3300, 3) eq 1100
capture {
averageIncome(5000, 0)
} eq "ArithmeticException: / by zero"
}
Wenn months null ist, wirft die Division in averageIncome() eine
ArithmeticException. Leider sagt uns dies nichts darüber, warum der Fehler
aufgetreten ist, was der Nenner bedeutet und ob er überhaupt null sein darf.
Dies ist eindeutig ein Fehler im Code—averageIncome() sollte mit einem months
von 0 so umgehen, dass ein Division durch Null Fehler vermieden wird.
Lassen Sie uns averageIncome() modifizieren, um mehr Informationen über die
Quelle des Problems zu liefern. Wenn months null ist, können wir keinen
normalen Ganzzahlwert als Ergebnis zurückgeben. Eine Strategie ist es, null
zurückzugeben:
// Exceptions/AverageIncomeWithNull.kt
package withnull
import atomictest.eq
fun averageIncome(income: Int, months: Int) =
if (months == 0)
null
else
income / months
fun main() {
averageIncome(3300, 3) eq 1100
averageIncome(5000, 0) eq null
}
Wenn eine Funktion null zurückgeben kann, verlangt Kotlin, dass Sie das Ergebnis überprüfen, bevor Sie es verwenden (dies wird in Nullable Typen behandelt). Selbst wenn Sie nur dem Benutzer eine Ausgabe anzeigen möchten, ist es besser zu sagen: “Es sind keine vollen Monatszeiträume vergangen,” anstatt “Ihr durchschnittliches Einkommen für den Zeitraum ist: null.”
Anstatt averageIncome() mit den falschen Argumenten auszuführen, können Sie eine Ausnahme auslösen – entkommen und einen anderen Teil des Programms zwingen, das Problem zu verwalten. Sie könnten die Standard-ArithmeticException zulassen, aber es ist oft nützlicher, eine spezifische Ausnahme mit einer detaillierten Fehlermeldung zu werfen. Wenn Ihre Anwendung nach ein paar Jahren im Einsatz plötzlich eine Ausnahme auslöst, weil eine neue Funktion averageIncome() aufruft, ohne die Argumente richtig zu überprüfen, werden Sie für diese Nachricht dankbar sein:
// Exceptions/AverageIncomeWithException.kt
package properexception
import atomictest.*
fun averageIncome(income: Int, months: Int) =
if (months == 0)
throw IllegalArgumentException( // [1]
"Months can't be zero")
else
income / months
fun main() {
averageIncome(3300, 3) eq 1100
capture {
averageIncome(5000, 0)
} eq "IllegalArgumentException: " +
"Months can't be zero"
}
-
[1] Beim Auslösen einer Ausnahme wird das Schlüsselwort
throwgefolgt von der Ausnahme, die ausgelöst werden soll, zusammen mit allen Argumenten, die sie möglicherweise benötigt. Hier verwenden wir die Standard-AusnahmeklasseIllegalArgumentException.
Ihr Ziel ist es, die nützlichsten Nachrichten zu generieren, um die Unterstützung Ihrer Anwendung in Zukunft zu vereinfachen. Später lernen Sie, Ihre eigenen Ausnahme-Typen zu definieren und sie spezifisch auf Ihre Umstände abzustimmen.
Übungen und Lösungen finden Sie unter www.AtomicKotlin.com.