Panic, Defer und Recover
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 57 von 107.
Während Go die Rückgabe von Fehlern betont, sind manche Situationen wirklich nicht behebbar. Für diese Fälle bietet Go panic, defer und recover – einen Mechanismus zur Behandlung außergewöhnlicher Umstände.
panic stoppt die normale Ausführung sofort. Es ist für Programmierfehler reserviert, wie den Zugriff auf einen Index außerhalb des gültigen Bereichs, nicht für erwartete Fehler wie ungültige Benutzereingaben:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}defer plant die Ausführung einer Funktion für den Zeitpunkt, an dem die umgebende Funktion zurückkehrt – sei es regulär oder aufgrund eines Panics. Verzögerte Aufrufe werden in umgekehrter Reihenfolge ausgeführt (Last-In, First-Out):
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// Ausgabe: Opening file, Processing..., Closing file
}recover fängt eine Panic ab und kehrt zur normalen Ausführung zurück. Es funktioniert nur innerhalb einer deferred-Funktion:
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}Dieses Muster wandelt ein Panic in einen regulären Fehler um, sodass Ihr Programm fortgesetzt werden kann. Verwenden Sie es sparsam – bevorzugen Sie die Rückgabe von Fehlern für erwartete Fehlerfälle und reservieren Sie Panic/Recover für wirklich außergewöhnliche Situationen wie einen beschädigten Zustand oder nicht behebbare Bedingungen.
Aufgabe
EinfachLassen Sie uns einen sicheren Divisionsrechner bauen, der demonstriert, wie man panic, defer und recover verwendet, um Ausnahmesituationen elegant zu handhaben. Ihr Rechner wird riskante Operationen versuchen und Panics in reguläre Fehler umwandeln, die normal verarbeitet werden können.
Sie werden Ihren Code in zwei Dateien organisieren:
calculator.go: Erstellen Sie die Divisionslogik mit Panic-Wiederherstellung.Implementieren Sie eine
Divide-Funktion, die zwei Ganzzahlen entgegennimmt und mit der Nachricht"division by zero"paniziert, wenn der Divisor Null ist. Andernfalls gibt sie das ganzzahlige Ergebnis der Division zurück.Implementieren Sie eine
SafeDivide-Funktion, die zwei Ganzzahlen entgegennimmt und sowohl ein ganzzahliges Ergebnis als auch einen Fehler zurückgibt. Diese Funktion sollte:defermit einer anonymen Funktion verwenden, um sich von jeder Panic zu erholen- Falls eine Panic abgefangen wird, wandeln Sie diese in einen Fehler mit dem Format
"calculation error: [panic message]"um - Die
Divide-Funktion intern aufrufen - Das Ergebnis und
nilbei Erfolg zurückgeben, oder Null und den Fehler, falls eine Panic abgefangen wurde
main.go: Lesen Sie zwei Ganzzahlen aus der Eingabe ein und verwenden SieSafeDivide, um die Berechnung sicher durchzuführen. Demonstrieren Sie außerdemdefer, indem Sie Bereinigungsnachrichten in der richtigen Reihenfolge ausgeben.Ihre Main-Funktion sollte:
- Zu Beginn
Starting calculationausgeben deferverwenden, um die Ausgabe vonCleanup completezu planen- Ein weiteres
deferverwenden, um die Ausgabe vonReleasing resourceszu planen SafeDividemit den Eingabewerten aufrufen- Entweder
Result: [value]oderError: [error message]basierend auf dem Ergebnis ausgeben
- Zu Beginn
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Erste Ganzzahl (Dividend)
- Zeile 2: Zweite Ganzzahl (Divisor)
Denken Sie daran, dass verzögerte Aufrufe (deferred calls) in umgekehrter Reihenfolge ausgeführt werden (Last-In, First-Out), sodass Ihre Bereinigungsnachrichten in der entgegengesetzten Reihenfolge erscheinen sollten, in der sie verzögert wurden.
Zum Beispiel sollte Ihre Ausgabe bei 20 und 4 wie folgt aussehen:
Starting calculation
Result: 5
Releasing resources
Cleanup completeUnd bei 10 und 0 sollte Ihre Ausgabe wie folgt aussehen:
Starting calculation
Error: calculation error: division by zero
Releasing resources
Cleanup completeSpickzettel
Go bietet panic, defer und recover für den Umgang mit außergewöhnlichen Umständen.
panic stoppt die normale Ausführung sofort und ist für nicht behebbare Programmierfehler reserviert:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}defer plant die Ausführung einer Funktion für den Zeitpunkt, an dem die umgebende Funktion zurückkehrt. Verzögerte Aufrufe werden in umgekehrter Reihenfolge ausgeführt (Last-In, First-Out):
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// Output: Opening file, Processing..., Closing file
}recover fängt ein Panic ab und kehrt zur normalen Ausführung zurück. Es funktioniert nur innerhalb einer verzögerten Funktion (deferred function):
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}Dieses Muster wandelt ein Panic in einen regulären Fehler um. Bevorzugen Sie die Rückgabe von Fehlern für erwartete Fehlerfälle und reservieren Sie Panic/Recover für wirklich außergewöhnliche Situationen.
Probier es selbst
package main
import (
"fmt"
)
func main() {
// Eingabe lesen
var dividend, divisor int
fmt.Scanln(÷nd)
fmt.Scanln(&divisor)
// Startmeldung ausgeben
fmt.Println("Starting calculation")
// TODO: defer verwenden, um die Nachricht "Cleanup complete" zu planen
// TODO: defer verwenden, um die Nachricht "Releasing resources" zu planen
// Zur Erinnerung: Verzögerte Aufrufe (deferred calls) werden in LIFO-Reihenfolge ausgeführt (last-in, first-out)
// TODO: SafeDivide mit den Eingabewerten aufrufen
// TODO: Entweder "Result: [value]" oder "Error: [error message]" ausgeben,
// basierend darauf, ob ein Fehler zurückgegeben wurde
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der Go OOP
Externe DateienGo Workspace & ModulePackages & ImportsExportierte vs. nicht-exportierte NamenEinführung in OOP in GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktor-FunktionenZusammenfassung – Einfacher Taschenrechner4Interfaces
Einführung in InterfacesImplizite ImplementierungInterface als VertragLeeres Interface (any)Type AssertionType SwitchInterface-KompositionStringer & Error InterfacesRückblick - Formen-Rechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf Paket-EbeneGetter- & Setter-MethodenInformation Hiding in GoZusammenfassung - Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterType ConstraintsGenerische StructsWorkaround für generische MethodenZusammenfassung – Generische Collection2Vertiefung: Typen & Structs
Basis- & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct TagsAnonyme StructsVerschachtelte StructsZero Values & StandardwerteZusammenfassung - Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen des Struct EmbeddingMethod PromotionEinbetten mehrerer StructsEmbedding vs. AggregationShadowing eingebetteter MethodenZusammenfassung – Mitarbeiter-Hierarchie8Fehlerbehandlung & OOP
Das error-InterfaceEigene FehlertypenError Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverZusammenfassung – Datei-Parser3Zeiger & Speicher
Zeiger-Grundlagen in GoZeiger auf StructsWertübergabe vs. ReferenzübergabeDie new() FunktionGarbage Collection in GoWiederholung – Linked List Builder6Polymorphismus in Go
Polymorphismus über InterfacesDuck Typing in GoInterface-ErfüllungsregelnPolymorphe CollectionsDependency InjectionZusammenfassung – Payment Processor9Nebenläufigkeit & OOP
Goroutines GrundlagenChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignZusammenfassung - Worker Pool