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

Gestione degli Errori in Go

In Go, la gestione degli errori e esplicita e basata su valori di ritorno, non su eccezioni. Ogni funzione che puo fallire restituisce un valore di tipo error come ultimo valore di ritorno. Questo approccio rende il flusso degli errori chiaro e prevedibile.

L’Interfaccia error

L’interfaccia error e una delle piu semplici in Go:

type error interface {
    Error() string
}

Qualsiasi tipo che implementa il metodo Error() string soddisfa l’interfaccia error.

Creare Errori

Con errors.New

Il modo piu semplice per creare un errore:

package main

import (
    "errors"
    "fmt"
)

func dividi(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("divisione per zero")
    }
    return a / b, nil
}

func main() {
    risultato, err := dividi(10, 0)
    if err != nil {
        fmt.Println("Errore:", err)
        return
    }
    fmt.Println("Risultato:", risultato)
}

Con fmt.Errorf

Per creare errori con messaggi formattati:

func cercaUtente(id int) (string, error) {
    if id <= 0 {
        return "", fmt.Errorf("ID utente non valido: %d", id)
    }
    // ... logica di ricerca
    return "Marco", nil
}

Controllare gli Errori

Il pattern standard in Go per il controllo degli errori:

risultato, err := qualcheFunzione()
if err != nil {
    // gestisci l'errore
    return err // oppure log, oppure gestisci diversamente
}
// usa il risultato

Questo pattern e onnipresente nel codice Go e rappresenta una delle sue caratteristiche piu distintive.

Error Wrapping con %w

A partire da Go 1.13, e possibile “avvolgere” (wrap) un errore in un altro per aggiungere contesto mantenendo l’errore originale:

package main

import (
    "errors"
    "fmt"
    "os"
)

func leggiConfigurazione(percorso string) ([]byte, error) {
    dati, err := os.ReadFile(percorso)
    if err != nil {
        return nil, fmt.Errorf("errore lettura configurazione: %w", err)
    }
    return dati, nil
}

func inizializza() error {
    _, err := leggiConfigurazione("config.json")
    if err != nil {
        return fmt.Errorf("inizializzazione fallita: %w", err)
    }
    return nil
}

func main() {
    err := inizializza()
    if err != nil {
        fmt.Println(err)
        // stampa: inizializzazione fallita: errore lettura configurazione: open config.json: ...
    }
}

Il verbo %w (wrap) crea una catena di errori che puo essere esaminata successivamente.

errors.Is

errors.Is controlla se un errore nella catena corrisponde a un errore specifico:

package main

import (
    "errors"
    "fmt"
    "os"
)

func main() {
    _, err := os.Open("file_inesistente.txt")
    if err != nil {
        // Controlla se l'errore (o un errore nella catena) e "file not found"
        if errors.Is(err, os.ErrNotExist) {
            fmt.Println("Il file non esiste")
        } else {
            fmt.Println("Errore sconosciuto:", err)
        }
    }
}

errors.Is attraversa l’intera catena di errori avvolti, cercando una corrispondenza.

errors.As

errors.As cerca nella catena di errori un errore di un tipo specifico e lo estrae:

package main

import (
    "errors"
    "fmt"
    "os"
)

func main() {
    _, err := os.Open("file_inesistente.txt")
    if err != nil {
        var pathErr *os.PathError
        if errors.As(err, &pathErr) {
            fmt.Println("Operazione fallita:", pathErr.Op)
            fmt.Println("Percorso:", pathErr.Path)
            fmt.Println("Errore:", pathErr.Err)
        }
    }
}

errors.As e utile quando si ha bisogno di accedere ai campi specifici di un tipo di errore personalizzato.

Tipi di Errore Personalizzati

E possibile creare tipi di errore personalizzati implementando l’interfaccia error:

package main

import (
    "fmt"
)

type ErroreValidazione struct {
    Campo    string
    Problema string
}

func (e *ErroreValidazione) Error() string {
    return fmt.Sprintf("validazione fallita per '%s': %s", e.Campo, e.Problema)
}

func validaEta(eta int) error {
    if eta < 0 {
        return &ErroreValidazione{
            Campo:    "eta",
            Problema: "non puo essere negativa",
        }
    }
    if eta > 150 {
        return &ErroreValidazione{
            Campo:    "eta",
            Problema: "valore non realistico",
        }
    }
    return nil
}

func main() {
    err := validaEta(-5)
    if err != nil {
        fmt.Println(err)
        // stampa: validazione fallita per 'eta': non puo essere negativa
    }
}

Errori Sentinella

Gli errori sentinella sono errori predefiniti a livello di pacchetto, usati come valori di confronto:

package main

import (
    "errors"
    "fmt"
)

// Errori sentinella del pacchetto
var (
    ErrNonTrovato    = errors.New("elemento non trovato")
    ErrNonAutorizzato = errors.New("accesso non autorizzato")
)

func cercaElemento(id int) (string, error) {
    if id == 0 {
        return "", ErrNonTrovato
    }
    return "elemento", nil
}

func main() {
    _, err := cercaElemento(0)
    if errors.Is(err, ErrNonTrovato) {
        fmt.Println("Gestione caso: elemento non trovato")
    }
}

Best Practice

  1. Gestisci gli errori dove hanno senso: non ignorare mai un errore senza una buona ragione.
// Male: errore ignorato
risultato, _ := funzioneRischiosa()

// Bene: errore gestito
risultato, err := funzioneRischiosa()
if err != nil {
    log.Printf("errore in funzioneRischiosa: %v", err)
    return err
}
  1. Aggiungi contesto con il wrapping: avvolgi gli errori per creare messaggi informativi.

  2. Restituisci presto: gestisci l’errore subito e restituiscilo, mantenendo il codice lineare.

func elabora() error {
    if err := passo1(); err != nil {
        return fmt.Errorf("passo1: %w", err)
    }
    if err := passo2(); err != nil {
        return fmt.Errorf("passo2: %w", err)
    }
    return nil
}
  1. Usa errori sentinella per condizioni note: definisci errori a livello di pacchetto per condizioni che i chiamanti dovranno controllare.

  2. Documenta gli errori restituiti: indica nella documentazione quali errori una funzione puo restituire.

Conclusione

La gestione degli errori in Go e esplicita, prevedibile e potente. L’uso di errors.Is e errors.As per esaminare catene di errori, il wrapping con %w per aggiungere contesto, e i tipi di errore personalizzati permettono di creare un sistema di gestione errori robusto. Seguire le best practice e fondamentale per scrivere codice Go affidabile e manutenibile.