Go no tiene excepciones. El mecanismo para gestionar fallos es el tipo error, una interfaz con un único método. Esta simplicidad fuerza a los programadores a tratar los errores donde ocurren, hace el flujo de control explícito y evita los bloques try/catch que en otros lenguajes ocultan la propagación de errores.
La interfaz error
type error interface {
Error() string
}
Cualquier tipo que implemente Error() string es un error. Puedes crear el tuyo propio o usar los helpers del paquete estándar.
errors.New y fmt.Errorf
import (
"errors"
"fmt"
)
var ErrDivisionPorCero = errors.New("división por cero")
func dividir(a, b float64) (float64, error) {
if b == 0 {
return 0, ErrDivisionPorCero
}
return a / b, nil
}
func calcular(a, b float64) (float64, error) {
resultado, err := dividir(a, b)
if err != nil {
return 0, fmt.Errorf("calcular(%v, %v): %w", a, b, err)
}
return resultado, nil
}
El verbo %w de fmt.Errorf envuelve el error original: añade contexto pero conserva el error interior para que se pueda recuperar.
errors.Is: comparar errores a través de capas
errors.Is desenvuelve la cadena de errores y compara con el destino. Funciona aunque el error haya sido envuelto varias veces:
resultado, err := calcular(10, 0)
if errors.Is(err, ErrDivisionPorCero) {
fmt.Println("es un error de división por cero")
// imprime aunque el error fue envuelto con contexto
}
fmt.Println(err)
// calcular(10, 0): división por cero
No uses err == ErrDivisionPorCero: si el error fue envuelto, la comparación directa falla.
errors.As: extraer un tipo de error concreto
Cuando el error tiene campos adicionales (código HTTP, número de línea, nombre de fichero...) necesitas errors.As para convertirlo al tipo concreto:
type ErrorValidacion struct {
Campo string
Mensaje string
}
func (e *ErrorValidacion) Error() string {
return fmt.Sprintf("validación: campo %q: %s", e.Campo, e.Mensaje)
}
func validar(nombre string) error {
if len(nombre) == 0 {
return fmt.Errorf("entrada inválida: %w", &ErrorValidacion{
Campo: "nombre",
Mensaje: "no puede estar vacío",
})
}
return nil
}
func main() {
err := validar("")
var ev *ErrorValidacion
if errors.As(err, &ev) {
fmt.Printf("Campo afectado: %sn", ev.Campo)
fmt.Printf("Mensaje: %sn", ev.Mensaje)
}
}
Crear tipos de error personalizados
type ErrorHTTP struct {
Codigo int
Mensaje string
}
func (e *ErrorHTTP) Error() string {
return fmt.Sprintf("HTTP %d: %s", e.Codigo, e.Mensaje)
}
func obtenerUsuario(id int) (*Persona, error) {
if id <= 0 {
return nil, &ErrorHTTP{Codigo: 400, Mensaje: "id inválido"}
}
if id == 99 {
return nil, &ErrorHTTP{Codigo: 404, Mensaje: "usuario no encontrado"}
}
return &Persona{Nombre: "Ana"}, nil
}
El patrón sentinel error
Un sentinel error es una variable exportada de tipo error que representa un estado concreto conocido. Los paquetes de la biblioteca estándar los usan ampliamente:
// io.EOF indica que no hay más datos que leer
// sql.ErrNoRows indica que la consulta no devolvió filas
if err == io.EOF {
// llegamos al final, no es un error real
}
if errors.Is(err, sql.ErrNoRows) {
// el registro no existe
}
