En Go, la gestión 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 minimalista.
Solo contiene un método, Error(), que devuelve el mensaje de error como un string.
type error interface {
Error() string
}
Siempre que definas una función en la que pueda producirse un error durante la ejecución que deba llegar a quien la llama, tienes que incluir error como uno de los tipos devueltos.
Si la función devuelve varios valores, por convención error es siempre 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 tienes que pasar es el mensaje de error como un string, y errors.New() se encargará de crear un valor que contenga tu mensaje e implemente la interfaz error.
Si la función devuelve un error, es buena práctica devolver el valor cero para el resto de los valores devueltos:
func DoSomething() (SomeStruct, int, error) {
// ...
return SomeStruct{}, 0, errors.New("failed to calculate result")
}
No des por sentado que todas las funciones devuelven valores cero en el resto de los valores devueltos cuando hay un error. Lo mejor es dar por sentado que no es seguro usar ninguno de los demás valores devueltos si se ha producido un error. Las únicas excepciones son los casos en los que la documentación indica claramente que los demás valores devueltos son significativos cuando hay un error.
Si quieres usar un error simple como este en varios sitios, deberías declarar una variable para el error en lugar de usar errors.New directamente.
Por convención, el nombre de la variable debe empezar por Err o err (según si es exportada o no).
A estas variables de error se las suele llamar errores centinela.
import "errors"
var ErrNotFound = errors.New("resource was not found")
func DoSomething() error {
// ...
return ErrNotFound
}
Devuelve nil como error para indicar que no hubo errores durante la ejecución de la función:
func Foo() (int, error) {
return 10, nil
}
Si llamas a una función que devuelve un error, lo habitual es guardar el valor del error en una variable llamada err.
Antes de usar el resultado real de la función, tienes que comprobar que no hubo ningún error.
Para evitar anidar el «camino feliz» de tu código, conviene gestionar primero los casos de error.
Podemos usar == y != para comparar el error con nil, y sabemos que hubo un error cuando err no es nil.
func processUserFile() error {
file, err := os.Open("./users.csv")
if err != nil {
return err
}
// do something with file
}
La mayoría de las veces, el error se devolverá hacia arriba por la pila de funciones, como se muestra en el ejemplo anterior. Otra forma de gestionar el error podría ser registrarlo y continuar con alguna otra operación. Es buena práctica devolver el error o registrarlo, pero nunca las dos cosas.
Como la mayoría de las funciones en Go incluyen un error como uno de los valores devueltos, 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 únicamente el mensaje de error, puedes crear un tipo de error personalizado.
Como se ha mencionado 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 en 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 motivo.
Ten en cuenta que esto significa que tienes que devolver un puntero a tu error personalizado, porque de lo contrario no contará como error, ya que 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: "...",
}
}
Por fin llegó el día que tanto esperabas y ahora eres el orgulloso propietario de una preciosa granja en los Alpes.
Sigues sin querer madrugar para alimentar a tus vacas.
Como eres un ingeniero excelente, construyes un dispensador de comida, el FEED-M-ALL.
Lo último que necesitas para terminar tu proyecto es un fragmento de código que calcule la cantidad de forraje que debe recibir cada vaca. Es importante que cada vaca reciba la misma cantidad; tienes que evitar conflictos. Las vacas son muy sensibles.
Por suerte, no necesitas calcular tú mismo todas las fórmulas para determinar las cantidades de forraje. Usas una misteriosa biblioteca externa que encontraste en internet. Se supone que da como resultado las vacas más felices. La biblioteca expone un tipo que cumple la siguiente interfaz. Te basarás en esto en el código que escribas tú mismo.
type FodderCalculator interface {
FodderAmount(int) (float64, error)
FatteningFactor() (float64, error)
}
A medida que trabajes en tu código, irás mejorando la gestión de errores para que sea más robusta y más fácil de depurar cuando lo uses en tu día a día en la granja.
En primer lugar, te centras en escribir el código necesario para calcular la cantidad de forraje por vaca.
Implementa una función DivideFood que acepte como argumentos un FodderCalculator y un número de vacas como entero.
Para esta tarea, supones que el número de vacas que se pasa siempre es mayor que cero.
La función debe devolver la cantidad de comida por vaca como un float64 o un error si se ha producido alguno.
Para hacer el cálculo, primero tienes que obtener la cantidad total de forraje para todas las vacas.
Esto se hace llamando al método FodderAmount y pasándole el número de vacas.
Además, necesitas un factor por el que hay que multiplicar esa cantidad.
Obtienes este factor llamando al método FatteningFactor.
Con estos dos valores y el número de vacas, ya puedes calcular la cantidad de comida por vaca (como un float64).
Eso es lo que debe devolver la función DivideFood.
Si uno de los métodos a los que llamas devuelve un error, la ejecución debe detenerse y ese error debe devolverse (tal cual) desde la función DivideFood.
// For this example, we assume FodderAmount returns 50
// and FatteningFactor returns 1.5.
DivideFood(fodderCalculator, 5)
// => 15 <nil>
// Now assuming FodderAmount returns an error with message "something went wrong".
DivideFood(fodderCalculator, 5)
// => 0 "something went wrong"
Mientras trabajabas en la primera tarea, te diste cuenta de que la biblioteca externa que usas no tiene tanta calidad como pensabas. Por ejemplo, no puede gestionar correctamente entradas no válidas. Quieres sortear esta limitación añadiendo una comprobación del valor de entrada en tu propio código.
Escribe una función ValidateInputAndDivideFood que tenga la misma firma que DivideFood de arriba.
DivideFood y devolver los resultados de esa llamada."invalid number of cows".ValidateInputAndDivideFood(fodderCalculator, 5)
// => 15 <nil>
ValidateInputAndDivideFood(fodderCalculator, -2)
// => 0 "invalid number of cows"
Comprobar el número de vacas antes de pasarlo fue un buen movimiento, pero no estás del todo contento con el mensaje de error tan poco concreto.
Decides hacerlo mejor creando un tipo de error personalizado llamado InvalidCowsError.
El error personalizado debe contener el número de vacas (int) y un mensaje personalizado (string), y el método Error debe serializar los datos con el siguiente formato:
{number of cows} cows are invalid: {custom message}
Con tu error personalizado en mano, implementa una función ValidateNumberOfCows que acepte el número de vacas como un entero y devuelva un error (o nil).
InvalidCowsError con el mensaje personalizado establecido en "there are no negative cows".InvalidCowsError con el mensaje personalizado establecido en "no cows don't need food".nil para indicar que la validación se ha realizado correctamente.err := ValidateNumberOfCows(-5)
err.Error()
// => "-5 cows are invalid: there are no negative cows"
Después del duro trabajo de preparar esta función de validación, te das cuenta de que ya ha caído la tarde y dejas tu escritorio para disfrutar de la puesta de sol sobre las montañas. Dejas para otro día la tarea de añadir de verdad la nueva función de validación a tu código.
Regístrate en Exercism para aprender y dominar Go con 34 conceptos166 ejercicios y mentoría humana real, todo gratis.