In Go werden deferred Aufrufe (defer) zusammen mit anonymen Closures verwendet, um eine garantierte Bereinigung oder den Abschluss der Arbeit mit Ressourcen sicherzustellen. Dieses Muster ermöglicht es, die Bereinigung logisch zu gruppieren und Fehler ordentlich zu behandeln, wodurch der Code lesbar und zuverlässig bleibt.
Hintergrund der Frage:
Defer stammt aus anderen Programmiersprachen und erleichtert das Leben von Go-Entwicklern erheblich. Die Kombination von defer und Closures ist zum Standard geworden, um die Bereinigung von Dateien, Verbindungen und anderen externen Ressourcen in Blöcken zu gewährleisten, in denen es viele Ausgänge (return, panic) geben kann.
Problem:
Bei komplexer Logik zur Bereinigung von Ressourcen (z. B. Dateien, Verbindungen, Speicherorte) ist es notwendig sicherzustellen, dass auch im Falle eines Fehlers oder beim Verlassen der Funktion die Bereinigung erfolgt. Bei unsachgemäßer Verwendung kann es zu Leaks, falscher Reihenfolge der Bereinigung oder sinnlosen Fehlern kommen.
Lösung:
Verwenden Sie defer mit einer anonymen Funktion (closure), um:
Beispielcode:
package main import ( "fmt" "os" ) func WriteFileDemo(filename string) (err error) { f, err := os.Create(filename) if err != nil { return } defer func() { cerr := f.Close() if cerr != nil && err == nil { err = cerr } }() // Arbeitslogik mit der Datei fmt.Fprintln(f, "Hello world") return // defer wird selbst bei return hier ausgeführt } func main() { if err := WriteFileDemo("test.txt"); err != nil { fmt.Println("Fehler:", err) } }
Wichtige Merkmale:
Wann werden die Variablen, die in einem deferred closure verwendet werden, fixiert — beim Anmelden von defer oder beim tatsächlichen Aufruf?
Sie werden beim Anmelden von defer fixiert, aber wenn das Closure auf Variablen per Referenz zugreift, werden die Werte zum Zeitpunkt der Ausführung von defer verwendet. Dies führt manchmal zu unerwarteten Ergebnissen.
for i := 0; i < 3; i++ { defer func() { fmt.Println(i) }() // wird 2, 2, 2 ausgeben }
Kann man Werte in das closure über Parameter übergeben, um die Referenzaufnahme zu vermeiden?
Ja, man kann Parameter für die anonyme Funktion deklarieren und die aktuellen Werte übergeben — dann werden die Werte als Kopien "eingefroren".
for i := 0; i < 3; i++ { defer func(n int) { fmt.Println(n) }(i) // wird 2, 1, 0 ausgeben }
Was tun, wenn in einem deferred closure eine Panik auftritt? Wie kann man damit umgehen?
Innerhalb des closures verwendet man die Funktion recover(), um zu verhindern, dass die Panik nach außen dringt, und sanfte Wiederherstellung der Funktionsfähigkeit zu realisieren.
defer func() { if r := recover(); r != nil { log.Println("Wiederhergestellt:", r) } }()
Der Code öffnet mehrere Dateien, vergisst jedoch, defer f.Close() zu setzen. Tritt ein Fehler auf, wird die Kontrolle zurückgegeben und ein Teil der Dateien bleibt unbereinigt, was zu Ressourcenausfällen führt.
Vorteile:
Nachteile:
Es wird ein deferred closure für alle Verkaufsoperationen verwendet: sorgfältige Schließung des Dateistreams und Behandlung von Fehlern, selbst wenn die Datei nicht vollständig geschrieben wurde.
Vorteile:
Nachteile: