Menu
Coddy logo textTech

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.

challenge icon

Wyzwanie

Łatwy

Zbudujmy 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ć defer z 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
  • main.go: Odczytaj z wejścia dwie liczby całkowite i użyj SafeDivide, aby bezpiecznie wykonać obliczenie. Pokaż też działanie defer, 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ć wypisanie Cleanup complete
    • Użyć kolejnego defer, aby zaplanować wypisanie Releasing resources
    • Wywołać SafeDivide z wartościami wejściowymi
    • W zależności od wyniku wypisać Result: [value] albo Error: [error message]

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 complete

A dla wartości 10 i 0 wynik powinien być następujący:

Starting calculation
Error: calculation error: division by zero
Releasing resources
Cleanup complete

Spróbuj swoich sił

package main

import (
	"fmt"
)

func main() {
	// Wczytaj dane wejściowe
	var dividend, divisor int
	fmt.Scanln(&dividend)
	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
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online