panic()
und recover()
Die Eingebaute Funktion panic()
wird meist in Situationen
genutzt, in der es keinen schlauen Ausweg gibt und ein Fehler überraschend
auftritt. Wenn beispielsweise eine Funktion keinen weiteren Hauptspeicher vom
Betriebssystem mehr anfordern kann, könnte der Programmteil als letzten Ausweg
einen Ausnahmezustand auslösen (mit panic()
). Der aufrufende Programmteil
könnte dann mit recover()
sicherstellen, dass dem
Anwender eine sinnvolle Fehlermeldung angezeigt wird und sich gegebenenfalls
»sauber« beenden. Wird die Funktion recover()
nicht ausgeführt, dann
bricht das Programm mit der Ausführung mit einem «Laufzeitfehler« ab.
Das folgende Beispiel zeigt, wie man panic()
benutzt. Natürlich ist es recht
sinnfrei für sich genommen:[1]
Wird das Programm ausgeführt, bricht Go an der Stelle bei panic()
ab und
erzeugt eine Meldung auf der Konsole:
$ go run main.go
panic: Laufzeitfehler!
goroutine 1 [running]:
main.myFunc()
/Users/patrick/prog/go/hello/main.go:6 +0x53
main.main()
/Users/patrick/prog/go/hello/main.go:10 +0x18
goroutine 2 [runnable]:
exit status 2
Bevor ich zeigen kann, wie man mit recover()
einen Laufzeitfehler abfangen
kann, muss die eingebaute Anweisung defer
eingeführt werden.
Mit defer
kann man Programmcode ausführen lassen, nachdem die Funktion verlassen wird.
Das ist sehr praktisch um Ressourcen wieder freizugeben, wenn man sie benutzt.
Öffnet man beispielsweise eine Datei, kann man mit der Anweisung defer
datei.Close()
sicherstellen, dass die Funktion Close()
nach Verlassen der
aktuellen Funktion ausgeführt wird, egal, wo oder wie die Funktion verlassen wird.
package main
import "fmt"
func hallo() {
fmt.Println("Hallo")
}
func main() {
defer hallo()
fmt.Println("Guten Tag")
}
Wenn man das Programm laufen lässt, wird als erstes Guten Tag
ausgegeben und
anschließend Hallo
(probiere es aus). Durch die Anweisung defer
wird
hallo()
sofort nach Beendigung der aktuellen Funktion (hier: main
)
aufgerufen. Werden mehrere defer
-Anweisungen angegeben, so werden die
zuletzt definierten defer
-Anweisungen zuerst aufgerufen, also genau in
umgekehrter Reihenfolge ihres Auftretens im Quellcode.
Da wir nun die Möglichkeit haben, eine Funktion garantiert aufzurufen nachdem
die aktuelle Funktion zuende ist, können wir das für die »Aufräumarbeit« mit
recover()
nutzen.
package main
import "fmt"
func myFunc() {
panic("Laufzeitfehler!")
}
// Wird wegen defer auf jeden Fall ausgeführt, auch wenn panic()
// nicht aufgerufen wurde. In dem Fall ist die Rückgabe von
// recover nil.
func myRecover() {
r := recover()
if r != nil {
fmt.Println("Es wurde eine Ausnahme ausgelöst:", r)
}
}
func main() {
defer myRecover()
myFunc()
}
Das ist wie unser erstes Beispiel mit panic()
, nur dass hier vor dem Aufruf
von myFunc()
noch sichergestellt wird, dass die Funktion myRecover()
nach
Verlassen von main()
ausgeführt wird. Die Funktion recover()
überprüft, ob
vorher ein Laufzeitfehler mit panic()
ausgelöst wurde und gibt dann den Wert
zurück, den wir beim Aufruf von panic()
übergeben haben (in unserem Fall die
Zeichenkette »Laufzeitfehler!«). Wurde kein Laufzeitfehler ausgelöst, ist die
Variable nil
, also der Wert für »nichts«. Daran können wir überprüfen, ob
wir etwas machen müssen. Ist der Rückgabewert, den wir in der Variablen r
speichern, nicht nil
(!=
bedeutet soviel wie »ungleich« oder »nicht
gleich«), dann können wir mit der Fehlerbehandlung starten. Ansonten wurde
panic()
nicht aufgerufen und es werden in dem Beispiel keine weiteren
Anweisungen ausgeführt, das Programm beendet sich.
panic()
oder Rückgabe vom Typ error
?
Wie beschrieben wird die Funktion panic()
dann benutzt, wenn eine
Fehlersituation überraschend aufgetreten ist und das Programm oder die
Unterroutine nicht sinnvoll fortgesetzt werden kann. Hingegen wird die
Rückgabe eines Wertes vom Typ error
immer dann genutzt, wenn ein Fehler zu
erwarten ist. Ein Programm könnte auch mit jedem anderen Typ einen
Fehlerzustand anzeigen, beispielsweise mit einer booleschen Variable, die auf
true
gesetzt wird, wenn ein Fehler aufgetreten ist.
Schlussendlich ist das ein wenig Geschmacksfrage, wie die Fehlerbahndlung in
einem Programm passiert. Die Standardbibliothek und wahrscheinlich alle
Pakete, die als OpenSource verfügbar sind, nutzen hauptsächlich die oben
aufgezeigten Varianten mit error
in normalen Fällen und panic()
in
Ausnahmesituationen. Hält man sich an die Konvention, wird man wohl keine
Überraschungen erleben, liest man seinen eigenen Code nach einiger Zeit Pause
wieder.
panic()
gibt es schlicht keinen Grund für eine fest eingebaute Ausnahmesituation, daher ist das Beispiel eben sinnlos, es soll aber auch nur das Prinzip von panic()
zeigen.