Ablaufsteuerung mit den Funktionen defer, panic und recover

Abgeschlossen

Sehen wir uns nun einige Ablaufsteuerungen an, die spezifisch für Go sind: defer, panic und recover. Jede dieser Funktionen verfügt über mehrere Anwendungsfälle. Hier untersuchen wir die wichtigsten Anwendungsfälle.

Defer-Funktion

In Go stellt eine defer-Anweisung die Ausführung einer Funktion (einschließlich aller Parameter) zurück, bis die Funktion abgeschlossen ist, die die defer-Anweisung enthält. Im Allgemeinen stellen Sie eine Funktion zurück, wenn Sie vermeiden möchten, dass Aufgaben wie das Schließen einer Datei oder das Ausführen eines Bereinigungsvorgangs vergessen werden.

Sie können beliebig viele Funktionen zurückstellen. Die defer-Anweisungen werden in umgekehrter Reihenfolge ausgeführt, von der letzten bis zur ersten Anweisung.

Überprüfen Sie, wie dieses Muster funktioniert, indem Sie den folgenden Beispielcode ausführen:

package main

import "fmt"

func main() {
    for i := 1; i <= 4; i++ {
        defer fmt.Println("deferred", -i)
        fmt.Println("regular", i)
    }
}

Hier ist die Codeausgabe:

regular 1
regular 2
regular 3
regular 4
deferred -4
deferred -3
deferred -2
deferred -1

Beachten Sie in diesem Beispiel, dass bei jeder Verzögerung von fmt.Println("deferred", -i) der Wert für i gespeichert und der Funktionsaufruf einer Warteschlange hinzugefügt wurde. Nachdem die main()-Funktion die Ausgabe der regular-Werte abgeschlossen hat, wurden alle zurückgestellten Aufrufe ausgeführt. Beachten Sie, dass die Ausgabe der verzögerten Aufrufe in umgekehrter Reihenfolge (LIFO, „last in, first out“) erfolgt, wenn sie aus der Warteschlange abgearbeitet werden.

Ein typischer Anwendungsfall für die defer-Funktion ist das Schließen einer Datei, nachdem Sie die Arbeit daran abgeschlossen haben. Hier sehen Sie ein Beispiel:

package main

import (
    "io"
    "os"
    "fmt"
)

func main() {
    newfile, error := os.Create("learnGo.txt")
    if error != nil {
        fmt.Println("Error: Could not create file.")
        return
    }
    defer newfile.Close()

    if _, error = io.WriteString(newfile, "Learning Go!"); error != nil {
	    fmt.Println("Error: Could not write to file.")
        return
    }

    newfile.Sync()
}

Nachdem Sie eine Datei erstellt oder geöffnet haben, stellen Sie die .Close()-Funktion zurück, damit Sie das Schließen der Datei nicht vergessen, wenn Sie fertig sind.

Panic-Funktion

Laufzeitfehler bewirken bei Go-Programmen eine Panik, etwa beim Versuch, durch einen Index außerhalb der Grenzen auf ein Array zuzugreifen oder einen Nullzeiger zu dereferenzieren. Sie können die Panik eines Programms auch erzwingen.

Die integrierte Funktion panic() beendet den normalen Ablauf der Steuerung in einem Go-Programm. Wenn Sie einen panic-Aufruf verwenden, werden alle zurückgestellten (deferred) Funktionsaufrufe normal ausgeführt. Der Prozess arbeitet den Stapel von unten nach oben ab, bis alle Funktionen zurückkehren. Das Programm stürzt dann mit einer Protokollnachricht ab. Die Nachricht enthält alle Fehlerinformationen und eine Stapelüberwachung, die Ihnen bei der Diagnose der Grundursache des Problems hilft.

Wenn Sie die panic()-Funktion aufrufen, können Sie einen beliebigen Wert als Argument hinzufügen. Normalerweise senden Sie eine Fehlermeldung, warum die Panik aufgetreten ist.

Beispielsweise sind im folgenden Code die Funktionen panic und defer kombiniert. Versuchen Sie, diesen Code auszuführen, um zu sehen, wie die Ablaufsteuerung unterbrochen wird. Beachten Sie, dass die Bereinigungsprozesse weiterhin ausgeführt werden.

package main

import "fmt"

func highlow(high int, low int) {
    if high < low {
        fmt.Println("Panic!")
        panic("highlow() low greater than high")
    }
    defer fmt.Println("Deferred: highlow(", high, ",", low, ")")
    fmt.Println("Call: highlow(", high, ",", low, ")")

    highlow(high, low + 1)
}

func main() {
    highlow(2, 0)
    fmt.Println("Program finished successfully!")
}

So sieht die Ausgabe aus:

Call: highlow( 2 , 0 )
Call: highlow( 2 , 1 )
Call: highlow( 2 , 2 )
Panic!
Deferred: highlow( 2 , 2 )
Deferred: highlow( 2 , 1 )
Deferred: highlow( 2 , 0 )
panic: highlow() low greater than high

goroutine 1 [running]:
main.highlow(0x2, 0x3)
	/tmp/sandbox/prog.go:13 +0x34c
main.highlow(0x2, 0x2)
	/tmp/sandbox/prog.go:18 +0x298
main.highlow(0x2, 0x1)
	/tmp/sandbox/prog.go:18 +0x298
main.highlow(0x2, 0x0)
	/tmp/sandbox/prog.go:18 +0x298
main.main()
	/tmp/sandbox/prog.go:6 +0x37

Program exited: status 2.

Folgendes passiert, wenn der Code ausgeführt wird:

  1. Alles wird normal ausgeführt. Das Programm gibt die der Funktion highlow() übergebenen hohen und niedrigen Werte aus.

  2. Wenn der Wert von low größer als der Wert von high ist, löst das Programm den Panikmodus aus. Die Nachricht Panic! wird angezeigt. An diesem Punkt wird die Ablaufsteuerung unterbrochen, und alle zurückgestellten Funktionen beginnen mit der Ausgabe der Nachricht Deferred....

  3. Das Programm stürzt ab, und Sie sehen die vollständige Stapelüberwachung. Die Nachricht Program finished successfully! wird nicht angezeigt.

Ein Aufruf von panic() wird normalerweise ausgeführt, wenn unerwartet schwerwiegende Fehler auftreten. Zum Vermeiden eines Programmabsturzes können Sie eine weitere Funktion namens recover() verwenden.

Recover-Funktion

Gelegentlich möchten Sie vielleicht einen Programmabsturz vermeiden und stattdessen den Fehler intern melden. Oder vielleicht möchten Sie das Problem beheben, bevor Sie das Programm abstürzen lassen. Sie können z. B. jede Verbindung mit einer Ressource schließen, um weitere Probleme zu vermeiden.

Go stellt die integrierte Funktion recover() bereit, mit der Sie nach einer Panik die Kontrolle wiedererlangen können. Sie rufen recover nur in einer Funktion auf, in der Sie auch defer aufrufen. Wenn Sie die Funktion recover() aufrufen, gibt sie nil zurück und hat bei der normalen Ausführung keine weiteren Auswirkungen.

Versuchen Sie, die main-Funktion im vorstehenden Code zu ändern, um einen Aufruf der recover()-Funktion hinzuzufügen. Beispiel:

func main() {
    defer func() {
	handler := recover()
        if handler != nil {
            fmt.Println("main(): recover", handler)
        }
    }()

    highlow(2, 0)
    fmt.Println("Program finished successfully!")
}

Wenn Sie das Programm ausführen, sollte die Ausgabe wie folgt aussehen:

Call: highlow( 2 , 0 )
Call: highlow( 2 , 1 )
Call: highlow( 2 , 2 )
Panic!
Deferred: highlow( 2 , 2 )
Deferred: highlow( 2 , 1 )
Deferred: highlow( 2 , 0 )
main(): recover from panic highlow() low greater than high

Program exited.

Sehen Sie den Unterschied zur vorherigen Version? Der Hauptunterschied ist, dass Sie der Fehler der Stapelüberwachung nicht mehr angezeigt wird.

In der Funktion main() stellen Sie eine anonyme Funktion zurück, in der Sie die Funktion recover() aufrufen. Beim Aufruf von recover() kann nil nicht zurückgegeben werden, wenn im Programm der Panik-Modus auftritt. Sie können hier etwas unternehmen, um das Durcheinander zu bereinigen, aber in diesem Fall erfolgt einfach eine Ausgabe.

Die Kombination der Funktionen panic und recover ist die idiomatische Art und Weise, wie Go mit Ausnahmen umgeht. In anderen Programmiersprachen wird der try/catch-Block verwendet. Go bevorzugt den Ansatz, den Sie hier untersucht haben.

Weitere Informationen finden Sie unter dem Vorschlag zum Hinzufügen einer integrierten try-Funktion in Go.