En Go, el manejo de errores no se hace mediante excepciones.
En su lugar, los errores son valores normales de tipos que implementan la interfaz error integrada.
La interfaz error es muy mínima.
Contiene un solo método, Error(), que devuelve el mensaje de error como un string.
type error interface {
Error() string
}
Cada vez que defines una función en la que podría ocurrir un error durante la ejecución y ese error necesita llegar a quien llama, debes incluir error como uno de los tipos de retorno.
Si la función tiene varios valores de retorno, por convención error siempre es el último.
func DoSomething() (int, error) {
// ...
}
No siempre tienes que implementar tú mismo la interfaz de error.
Para crear un error simple, puedes usar la función errors.New(), que forma parte del paquete errors de la biblioteca estándar.
Lo único que necesitas pasar es el mensaje de error como un string, y errors.New() se encargará de crear un valor que contiene tu mensaje e implementa la interfaz error.
Si la función devuelve un error, es buena práctica devolver el valor cero para todos los demás parámetros de retorno:
func DoSomething() (SomeStruct, int, error) {
// ...
return SomeStruct{}, 0, errors.New("failed to calculate result")
}
No debes asumir que todas las funciones devuelven valores cero para los demás valores de retorno cuando hay un error. La mejor práctica es asumir que no es seguro usar ninguno de los otros valores de retorno si se devuelve un error.
Como ejemplo, imagina que una función intentaba leer de un archivo y devuelve un slice de bytes a medio llenar y un error. Si reutilizas ese slice de bytes devuelto en tu código, asumiendo que siempre está vacío porque hubo un error, puedes toparte con bugs sutiles.
Las únicas excepciones son los casos en los que la documentación de la función indica claramente que otros valores de retorno son significativos en caso de error.
Mira la [documentación de Write en un buffer][buffer-write] para ver un ejemplo.
Asegúrate de que el mensaje de error que proporciones sea lo más específico posible, ya que los errores no incluyen ninguna traza de pila de forma predeterminada. Por convención, el mensaje de error debe empezar con una letra minúscula y no terminar con un punto.
Si quieres usar ese error en varios lugares (o quieres poner el error a disposición de quien consume tu paquete), deberías declarar una variable para el error en lugar de usar errors.New en línea.
Por convención, el nombre de la variable debe empezar con Err o err (según si es exportada o no).
Estas variables de error a menudo se llaman errores centinela.
import "errors"
var ErrNotFound = errors.New("resource was not found")
func DoSomething() error {
// ...
return ErrNotFound
}
Devuelve nil para el error para indicar que no hubo errores durante la ejecución de la función:
func Foo() (int, error) {
return 10, nil
}
Si necesitas construir un mensaje de error más complejo a partir de algunas variables, puedes hacer uso del formateo de strings.
Go tiene una función especial, fmt.Errorf, para este propósito.
Construye el string del mensaje de error y devuelve un error.
input := 123
action := "UPDATE"
err := fmt.Errorf("invalid input %d for action %s", input, action)
err.Error()
// => "invalid input 123 for action UPDATE"
Si llamas a una función que devuelve un error, es común guardar el valor del error en una variable llamada err.
Antes de usar el resultado real de la función, debes comprobar que no hubo ningún error.
Podemos usar == y != para comparar el error con nil, y sabemos que hubo un error cuando err no es nil.
Para evitar anidar el «happy path» de tu código, los casos de error deben manejarse primero, por lo general mediante un retorno temprano. Consulta este famoso artículo de Mat Ryer para más detalles.
// Do this:
func myFunc() error {
file, err := os.Open("./users.csv")
if err != nil {
return err
}
// do something with file
}
// Not this:
func myFunc() error {
file, err := os.Open("./users.csv")
if err != nil {
// handle err
} else {
// do something with file
}
}
// Also not this:
func myFunc() error {
file, err := os.Open("./users.csv")
if err == nil {
// do something with file
}
// handle err
}
La mayoría de las veces, el error se devolverá hacia arriba por la pila de funciones, como se muestra en el primer ejemplo de arriba. Otra forma de manejar el error podría ser registrarlo y continuar con alguna otra operación. Es buena práctica devolver o registrar el error, nunca ambos.
Como la mayoría de las funciones en Go incluyen un error como uno de los valores de retorno, verás y usarás el patrón if err != nil por todas partes en el código de Go.
Si quieres que tu error incluya más información que solo el string del mensaje de error, puedes crear un tipo de error personalizado.
Como se mencionó antes, todo lo que implemente la interfaz error (es decir, que tenga un método Error() string) puede servir como error en Go.
Normalmente, se usa una estructura para crear un tipo de error personalizado.
Por convención, los nombres de los tipos de error personalizados deben terminar con Error.
Además, lo mejor es definir el método Error() string con un receptor de puntero; consulta este comentario de Stackoverflow para entender el razonamiento.
Ten en cuenta que esto significa que debes devolver un puntero a tu error personalizado; de lo contrario, no contará como error, porque el valor no puntero no proporciona el método Error() string.
type MyCustomError struct {
message string
details string
}
func (e *MyCustomError) Error() string {
return fmt.Sprintf("%s, details: %s", e.message, e.details)
}
func someFunction() error {
// ...
return &MyCustomError{
message: "...",
details: "...",
}
}