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]

package main

func myFunc() {
    panic("Laufzeitfehler!")
}

func main() {
    myFunc()
}

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.


1. Das ist leider immer das Problem mit Beispielen. Sie müssen so kurz sein, dass der Leser sofort erkennt, worum es geht. Meist ergibt sich aber dann nicht die Sinnhaftigkeit einer bestimmten Vorgehensweise. In dem Beispiel mit 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.