00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Panic e Recover in Go

In Go, panic e recover sono meccanismi per gestire situazioni eccezionali e irrecuperabili. A differenza delle eccezioni in altri linguaggi, in Go si preferisce la gestione esplicita degli errori; panic e recover sono riservati a casi veramente critici.

Cos’e il Panic

Un panic interrompe immediatamente l’esecuzione normale della funzione corrente. Tutte le funzioni defer vengono eseguite, poi il programma risale lo stack delle chiamate ripetendo il processo fino a terminare con un messaggio di errore:

package main

import "fmt"

func main() {
    fmt.Println("Inizio")
    panic("qualcosa e andato molto male!")
    fmt.Println("Questa riga non viene mai eseguita")
}

Output:

Inizio
panic: qualcosa e andato molto male!

goroutine 1 [running]:
main.main()
        /main.go:6 +0x...
exit status 2

Quando si Verifica un Panic

Go genera automaticamente un panic in diverse situazioni:

// Accesso fuori dai limiti di uno slice
s := []int{1, 2, 3}
fmt.Println(s[10]) // panic: runtime error: index out of range

// Dereferenziazione di un puntatore nil
var p *int
fmt.Println(*p) // panic: runtime error: invalid memory address

// Invio su un canale chiuso
ch := make(chan int)
close(ch)
ch <- 42 // panic: send on closed channel

// Asserzione di tipo errata
var i interface{} = "ciao"
n := i.(int) // panic: interface conversion: string, not int

Usare Recover con Defer

recover e una funzione built-in che permette di “catturare” un panic e riprendere il controllo. Funziona solo all’interno di una funzione defer:

package main

import "fmt"

func funzionePericolosa() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recuperato dal panic:", r)
        }
    }()

    fmt.Println("Prima del panic")
    panic("errore critico!")
    fmt.Println("Dopo il panic") // non viene mai eseguito
}

func main() {
    funzionePericolosa()
    fmt.Println("Il programma continua normalmente")
}

Output:

Prima del panic
Recuperato dal panic: errore critico!
Il programma continua normalmente

Recover Restituisce un Valore

recover() restituisce il valore passato a panic(). Se non c’e stato un panic, restituisce nil:

defer func() {
    r := recover()
    if r == nil {
        fmt.Println("Nessun panic")
        return
    }

    // Il valore puo essere di qualsiasi tipo
    switch v := r.(type) {
    case string:
        fmt.Println("Panic con messaggio:", v)
    case error:
        fmt.Println("Panic con errore:", v)
    default:
        fmt.Println("Panic con valore sconosciuto:", v)
    }
}()

Quando Usare Panic vs Error

Usa error (la norma in Go)

  • Per condizioni di errore prevedibili e gestibili
  • Per errori legati all’input dell’utente
  • Per errori di rete, file non trovati, ecc.
  • Per qualsiasi errore che il chiamante puo voler gestire
func dividi(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("divisione per zero")
    }
    return a / b, nil
}

Usa panic (eccezione, non la norma)

  • Per errori di programmazione (bug nel codice)
  • Per condizioni che non dovrebbero mai verificarsi
  • Per l’inizializzazione di un programma che non puo procedere
  • Per violazioni di invarianti interne
func deveEsserePositivo(n int) {
    if n <= 0 {
        panic(fmt.Sprintf("valore deve essere positivo, ricevuto: %d", n))
    }
}

Pattern nel Mondo Reale

Proteggere un Server HTTP

Un pattern molto comune e proteggere i gestori HTTP da panic imprevisti:

package main

import (
    "fmt"
    "log"
    "net/http"
)

func middlewareRecovery(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("Panic nel gestore: %v", err)
                http.Error(w, "Errore interno del server", http.StatusInternalServerError)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

func gestorePericoloso(w http.ResponseWriter, r *http.Request) {
    panic("qualcosa e andato storto!")
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", gestorePericoloso)

    server := middlewareRecovery(mux)
    fmt.Println("Server in ascolto su :8080")
    log.Fatal(http.ListenAndServe(":8080", server))
}

Convertire Panic in Errore

Un pattern utile per librerie che vogliono trasformare un panic interno in un errore restituito:

func operazioneSicura() (err error) {
    defer func() {
        if r := recover(); r != nil {
            switch v := r.(type) {
            case error:
                err = fmt.Errorf("operazione fallita: %w", v)
            default:
                err = fmt.Errorf("operazione fallita: %v", v)
            }
        }
    }()

    // codice che potrebbe generare un panic
    eseguiOperazioneRischiosa()
    return nil
}

Init con Must

Un pattern comune per l’inizializzazione e il prefisso Must, che genera un panic se l’inizializzazione fallisce:

func MustCompile(pattern string) *regexp.Regexp {
    re, err := regexp.Compile(pattern)
    if err != nil {
        panic(fmt.Sprintf("pattern non valido: %s: %v", pattern, err))
    }
    return re
}

// Usato a livello di pacchetto
var emailRegex = MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)

Questo pattern e usato nella libreria standard di Go, ad esempio in regexp.MustCompile e template.Must.

Conclusione

Panic e recover sono strumenti potenti ma da usare con parsimonia in Go. La filosofia del linguaggio privilegia la gestione esplicita degli errori tramite valori di ritorno. Usa panic solo per errori di programmazione e condizioni veramente irrecuperabili, e recover per proteggere i confini critici del programma come i server HTTP. Seguire questo principio porta a codice Go piu robusto e prevedibile.