Errori Personalizzati in Go
Go permette di creare errori personalizzati implementando l’interfaccia error. Questo è utile per fornire contesto aggiuntivo e permettere al chiamante di gestire errori specifici.
L’Interfaccia error
L’interfaccia error in Go è molto semplice:
type error interface {
Error() string
}
Qualsiasi tipo che implementa il metodo Error() string è un errore valido.
Errori Semplici
Per errori semplici, usa errors.New o fmt.Errorf:
import "errors"
var ErrNonTrovato = errors.New("elemento non trovato")
var ErrNonAutorizzato = errors.New("accesso non autorizzato")
func cerca(id int) (string, error) {
if id <= 0 {
return "", ErrNonTrovato
}
return "risultato", nil
}
Errori Sentinella
Gli errori sentinella sono variabili di errore predefinite che possono essere confrontate:
var (
ErrDivisionePerZero = errors.New("divisione per zero")
ErrFuoriRange = errors.New("indice fuori range")
)
func dividi(a, b float64) (float64, error) {
if b == 0 {
return 0, ErrDivisionePerZero
}
return a / b, nil
}
// Confronto con errors.Is
_, err := dividi(10, 0)
if errors.Is(err, ErrDivisionePerZero) {
fmt.Println("Impossibile dividere per zero")
}
Tipo di Errore Personalizzato
Per errori che necessitano di contesto aggiuntivo, crea un tipo struct:
type ErroreValidazione struct {
Campo string
Valore string
Messagio string
}
func (e *ErroreValidazione) Error() string {
return fmt.Sprintf("validazione fallita per %s='%s': %s",
e.Campo, e.Valore, e.Messagio)
}
func validaEmail(email string) error {
if !strings.Contains(email, "@") {
return &ErroreValidazione{
Campo: "email",
Valore: email,
Messagio: "deve contenere @",
}
}
return nil
}
Usare errors.As per Estrarre il Tipo
err := validaEmail("invalida")
var errVal *ErroreValidazione
if errors.As(err, &errVal) {
fmt.Println("Campo:", errVal.Campo)
fmt.Println("Valore:", errVal.Valore)
}
Wrapping degli Errori
Avvolgi gli errori per aggiungere contesto mantenendo l’errore originale:
func caricaUtente(id int) (*Utente, error) {
dati, err := leggiDaDB(id)
if err != nil {
return nil, fmt.Errorf("caricaUtente id=%d: %w", id, err)
}
return parsaUtente(dati)
}
// L'errore originale è ancora accessibile con errors.Is e errors.As
err := caricaUtente(42)
if errors.Is(err, sql.ErrNoRows) {
fmt.Println("Utente non trovato nel database")
}
Implementare Unwrap
Per errori personalizzati con wrapping, implementa il metodo Unwrap:
type ErroreOperazione struct {
Op string
Err error
}
func (e *ErroreOperazione) Error() string {
return fmt.Sprintf("operazione %s fallita: %v", e.Op, e.Err)
}
func (e *ErroreOperazione) Unwrap() error {
return e.Err
}
Best Practice
- Usa errori sentinella per condizioni note e confrontabili
- Usa tipi personalizzati quando serve contesto aggiuntivo
- Avvolgi gli errori con
%wper mantenere la catena degli errori - Controlla gli errori con
errors.Is(per valore) eerrors.As(per tipo) - Non esporre dettagli interni all’utente finale
Conclusione
Gli errori personalizzati in Go offrono un meccanismo potente e flessibile per gestire le condizioni di errore. Dalla semplice stringa ai tipi complessi con wrapping, Go fornisce gli strumenti per creare una gerarchia di errori chiara e manutenibile.