Panic, Defer i Recover
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 57 z 107.
Chociaż Go kładzie nacisk na zwracanie błędów, niektórych sytuacji naprawdę nie da się odzyskać. W takich przypadkach Go udostępnia panic, defer i recover: mechanizm obsługi wyjątkowych okoliczności.
panic natychmiast zatrzymuje normalne wykonanie. Jest zarezerwowane dla błędów programistycznych, takich jak dostęp do indeksu poza zakresem, a nie dla oczekiwanych błędów, takich jak nieprawidłowe dane wejściowe użytkownika:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}defer planuje uruchomienie funkcji, gdy otaczająca ją funkcja zakończy działanie, zarówno normalnie, jak i z powodu paniki. Odroczone wywołania są wykonywane w odwrotnej kolejności (ostatnie weszło, pierwsze wyszło):
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// Wynik: Opening file, Processing..., Closing file
}recover przechwytuje panikę i przywraca normalne działanie programu. Działa tylko wewnątrz odroczonej funkcji:
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}Ten wzorzec przekształca panikę w zwykły błąd, pozwalając programowi działać dalej. Używaj go oszczędnie: w przypadku spodziewanych błędów lepiej zwracać błędy, a panic/recover rezerwuj dla naprawdę wyjątkowych sytuacji, takich jak uszkodzony stan lub warunki uniemożliwiające odzyskanie działania.
Wyzwanie
ŁatwyZbudujmy kalkulator bezpiecznego dzielenia, który pokazuje, jak używać panic, defer i recover, aby sprawnie obsługiwać sytuacje wyjątkowe. Twój kalkulator będzie podejmował ryzykowne operacje i zamieniał paniki na zwykłe błędy, które można obsługiwać w standardowy sposób.
Podzielisz kod na dwa pliki:
calculator.go: Utwórz logikę dzielenia z odzyskiwaniem po panice.Zaimplementuj funkcję
Divide, która przyjmuje dwie liczby całkowite i wywołuje panikę z komunikatem"division by zero", jeśli dzielnik wynosi zero. W przeciwnym razie zwraca wynik dzielenia jako liczbę całkowitą.Zaimplementuj funkcję
SafeDivide, która przyjmuje dwie liczby całkowite i zwraca zarówno wynik będący liczbą całkowitą, jak i błąd. Ta funkcja powinna:- Użyć
deferz funkcją anonimową, aby odzyskać kontrolę po każdej panice - Jeśli uda się odzyskać kontrolę po panice, zamienić ją na błąd w formacie
"calculation error: [panic message]" - Wewnątrz wywołać funkcję
Divide - W przypadku powodzenia zwrócić wynik i
nil, a jeśli udało się odzyskać kontrolę po panice — zero i błąd
- Użyć
main.go: Odczytaj z wejścia dwie liczby całkowite i użyjSafeDivide, aby bezpiecznie wykonać obliczenie. Pokaż też działaniedefer, wypisując komunikaty o sprzątaniu we właściwej kolejności.Twoja funkcja main powinna:
- Na początku wypisać
Starting calculation - Użyć
defer, aby zaplanować wypisanieCleanup complete - Użyć kolejnego
defer, aby zaplanować wypisanieReleasing resources - Wywołać
SafeDividez wartościami wejściowymi - W zależności od wyniku wypisać
Result: [value]alboError: [error message]
- Na początku wypisać
Dane wejściowe będą następujące:
- Wiersz 1: Pierwsza liczba całkowita (dzielna)
- Wiersz 2: Druga liczba całkowita (dzielnik)
Pamiętaj, że wywołania odroczone są wykonywane w odwrotnej kolejności (ostatnie weszło, pierwsze wyszło), więc komunikaty o sprzątaniu powinny pojawić się w kolejności odwrotnej do tej, w której zostały odroczone.
Na przykład dla wartości 20 i 4 wynik powinien być następujący:
Starting calculation
Result: 5
Releasing resources
Cleanup completeA dla wartości 10 i 0 wynik powinien być następujący:
Starting calculation
Error: calculation error: division by zero
Releasing resources
Cleanup completeSpróbuj swoich sił
package main
import (
"fmt"
)
func main() {
// Wczytaj dane wejściowe
var dividend, divisor int
fmt.Scanln(÷nd)
fmt.Scanln(&divisor)
// Wyświetl komunikat początkowy
fmt.Println("Starting calculation")
// TODO: Użyj defer, aby zaplanować wyświetlenie komunikatu "Cleanup complete"
// TODO: Użyj defer, aby zaplanować wyświetlenie komunikatu "Releasing resources"
// Pamiętaj: odroczone wywołania wykonują się w kolejności LIFO (ostatnie weszło, pierwsze wyszło)
// TODO: Wywołaj SafeDivide z wartościami wejściowymi
// TODO: Wyświetl "Result: [value]" albo "Error: [error message]"
// w zależności od tego, czy zwrócono błąd
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy OOP w Go
Pliki zewnętrzneObszar roboczy i moduły GoPakiety i importyNazwy eksportowane i nieeksportowaneWprowadzenie do OOP w GoStruktury jako klasyDefiniowanie metod dla strukturOdbiorniki wskaźnikowe i wartościoweInicjalizacja strukturFunkcje konstruktoraPodsumowanie – prosty kalkulator4Interfejsy
Wprowadzenie do interfejsówImplementacja niejawnaInterfejs jako kontraktPusty interfejs (any)Asercja typuPrzełącznik typówKompozycja interfejsówInterfejsy Stringer i ErrorPowtórka — kalkulator figur7Hermetyzacja
Pola eksportowane i nieeksportowaneHermetyzacja na poziomie pakietuMetody pobierające i ustawiająceUkrywanie informacji w GoPowtórka – dane studentów10Typy generyczne (Go 1.18+)
Wprowadzenie do typów generycznychParametry typówOgraniczenia typówStruktury generyczneObejście ograniczeń metod generycznychPodsumowanie — generyczna kolekcja2Typy i struktury w praktyce
Typy proste i złożoneDefiniowanie własnych typówTagi pól strukturStruktury anonimoweStruktury zagnieżdżoneWartości zerowe i domyślnePodsumowanie – książka kontaktów5Kompozycja zamiast dziedziczenia
Dlaczego Go nie ma dziedziczeniaPodstawy osadzania strukturPromowanie metodOsadzanie wielu strukturOsadzanie a agregacjaPrzesłanianie osadzonych metodPodsumowanie — hierarchia pracowników8Obsługa błędów i programowanie obiektowe
Interfejs błęduWłasne typy błędówOpakowywanie błędów (fmt.Errorf)Błędy wartowniczeerrors.Is() i errors.As()Panic, Defer i RecoverPodsumowanie — parser plików3Wskaźniki i pamięć
Podstawy wskaźników w GoWskaźniki do strukturPrzekazywanie przez wartość a przez referencjęFunkcja new()Odśmiecanie pamięci w GoPowtórka — kreator listy wiązanej6Polimorfizm w Go
Polimorfizm za pomocą interfejsówDuck typing w GoZasady spełniania interfejsówKolekcje polimorficzneWstrzykiwanie zależnościPowtórka — procesor płatności9Współbieżność i OOP
Podstawy goroutinesKanały i komunikacjaKanały buforowane i niebuforowaneInstrukcja selectsync.Mutex i sync.RWMutexsync.WaitGroupProjektowanie struktur bezpiecznych wątkowoPowtórka – pula workerów12Zaawansowane zagadnienia programowania obiektowego
Wzorzec opcji funkcyjnychWzorzec Builder w GoŁańcuchowe wywoływanie metodAliasy typów a definicje typówPodstawy refleksjiPrzegląd generowania koduPoćwicz samodzielnie: Kompilator Go online