En Go, el manejo de errores no se hace con excepciones.
En su lugar, los errores son valores normales de tipos que implementan la interfaz error incorporada.
La interfaz error es muy minimalista.
Contiene un solo método, Error(), que devuelve el mensaje de error como string.
type error interface {
Error() string
}
Cada vez que defines una función en la que puede ocurrir un error durante la ejecución que necesita llegar a quien la llama, debes incluir error como uno de los tipos de retorno.
Si la función tiene varios valores de retorno, por convención error es siempre el último.
func DoSomething() (int, error) {
// ...
}
No siempre tienes que implementar tú 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 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. Lo mejor es asumir que no es seguro usar ninguno de los otros valores de retorno si ocurrió un error. Las únicas excepciones son los casos en los que la documentación indica claramente que los demás valores de retorno son significativos en caso de error.
Si quieres usar un error simple como este en varios lugares, debes 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).
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, 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.
Para evitar anidar el flujo normal de tu código, conviene manejar 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 en la pila de funciones, como se muestra en el ejemplo anterior. 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 ambas cosas.
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 mensaje de error como string, 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 un struct 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 puntero; consulta este comentario de Stack Overflow 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 que no es 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: "...",
}
}
El día que tanto esperaste finalmente llegó y ahora eres el orgulloso dueño de una hermosa granja en los Alpes.
Todavía no te gusta levantarte muy temprano para alimentar a tus vacas.
Como eres un excelente ingeniero, construyes un dispensador de comida, el FEED-M-ALL.
Lo último que se necesita para terminar tu proyecto es un fragmento de código que calcule la cantidad de forraje que debería recibir cada vaca. Es importante que cada vaca reciba la misma cantidad, hay que evitar conflictos. Las vacas son muy sensibles.
Por suerte, no necesitas deducir todas las fórmulas para calcular las cantidades de forraje por tu cuenta. 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 con la siguiente interfaz. Te apoyarás en esto en el código que escribas.
type FodderCalculator interface {
FodderAmount(int) (float64, error)
FatteningFactor() (float64, error)
}
A medida que trabajes en tu código, mejorarás el manejo de errores para que sea más robusto y más fácil de depurar cuando lo uses en tu vida diaria en la granja.
Primero que nada, te enfocas en escribir el código necesario para calcular la cantidad de forraje por vaca.
Implementa una función DivideFood que acepte un FodderCalculator y un número de vacas como entero como argumentos.
Para esta tarea, asumes 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 ocurrió alguno.
Para hacer el cálculo, primero necesitas obtener la cantidad total de forraje para todas las vacas.
Esto se hace llamando al método FodderAmount y pasando el número de vacas.
Además, necesitas un factor por el cual 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 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 la calidad que pensabas. Por ejemplo, no puede manejar correctamente los argumentos inválidos. Quieres sortear esta limitación agregando una verificación del argumento en tu propio código.
Escribe una función ValidateInputAndDivideFood que tenga la misma firma que DivideFood.
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"
Verificar el número de vacas antes de pasarlo fue una buena decisión, pero no estás del todo conforme con el mensaje de error tan poco específico.
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 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 fue exitosa.err := ValidateNumberOfCows(-5)
err.Error()
// => "-5 cows are invalid: there are no negative cows"
Después del duro trabajo de configurar esta función de validación, notas que ya es de noche y dejas tu escritorio para disfrutar del atardecer sobre las montañas. Dejas para otro día la tarea de agregar la nueva función de validación a tu código.
Regístrate en Exercism para aprender y dominar Go con 34 conceptos165 ejercicios y mentoría humana real, todo gratis.