Manejo de errores en Go

El lenguaje Go proporciona un mecanismo de manejo de errores muy simple mediante su interfaz de error integrada.

El manejo de errores en Go adopta el enfoque de devolver errores explícitamente, en lugar del mecanismo tradicional de excepciones. Este diseño hace que la lógica del código sea más clara y permite a los desarrolladores manejar errores de manera explícita en tiempo de compilación o ejecución.

El manejo de errores en Go se desarrolla principalmente en torno a los siguientes mecanismos:

  1. errorInterfaz: representación estándar de errores.
  2. Valor de retorno explícito: devolver errores mediante el valor de retorno de la función.
  3. Errores personalizados: se pueden crear errores mediante la biblioteca estándar o de forma personalizada.
  4. panicyrecover: manejar errores graves irrecuperables.

Interfaz error

La biblioteca estándar de Go define una interfaz error, que representa una abstracción de un error.

El tipo error es un tipo de interfaz, esta es su definición:

type error interface {
    Error() string
}
  • ImplementaciónerrorInterfaz: cualquier tipo que implementeError()el método puede ser usado como error.
  • Error()El método devuelve una cadena que describe el error.

Usar el paquete errors para crear errores

En la codificación, podemos generar mensajes de error implementando el tipo de interfaz error.

Crear un error simple:

Ejemplo

package main

import (
    "errors"
    "fmt"
)

func main() {
    err := errors.New("this is an error")
    fmt.Println(err) // Salida: this is an error
}

Las funciones suelen devolver información de error en el último valor de retorno; se puede usar errors.New para devolver un mensaje de error:

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // 实现
}

En el siguiente ejemplo, al llamar a Sqrt pasamos un número negativo, y entonces obtenemos un objeto error no nil. Comparamos este objeto con nil, el resultado es true, por lo que se llama a fmt.Println (el paquete fmt llama al método Error al procesar errores) para mostrar el error. Vea el código de ejemplo a continuación:

result, err:= Sqrt(-1)

if err != nil {
   fmt.Println(err)
}

Retorno explícito de errores

En Go, los errores generalmente se devuelven como valores de retorno de las funciones, y el desarrollador debe verificarlos y manejarlos explícitamente.

Retorno explícito de errores:

Ejemplo

package main

import (
        "errors"
        "fmt"
)

func divide(a, b int) (int, error) {
        if b == 0 {
                return 0, errors.New("division by zero")
        }
        return a / b, nil
}

func main() {
        result, err := divide(10, 0)
        if err != nil {
                fmt.Println("Error:", err)
        } else {
                fmt.Println("Result:", result)
        }
}

Salida:

Error: division by zero

Errores personalizados

Al definir tipos personalizados, se puede extender la interfaz error.

Tipo de error personalizado:

Ejemplo

package main

import (
        "fmt"
)

type DivideError struct {
        Dividend int
        Divisor  int
}

func (e *DivideError) Error() string {
        return fmt.Sprintf("cannot divide %d by %d", e.Dividend, e.Divisor)
}

func divide(a, b int) (int, error) {
        if b == 0 {
                return 0, &DivideError{Dividend: a, Divisor: b}
        }
        return a / b, nil
}

func main() {
        _, err := divide(10, 0)
        if err != nil {
                fmt.Println(err) // Salida: cannot divide 10 by 0
        }
}

El paquete fmt y el formato de errores

fmtEl paquete proporciona soporte para la salida formateada de errores:

  • %v: formato predeterminado.
  • %+v: si es compatible, muestra información detallada del error.
  • %s: se muestra como una cadena.

Ejemplo

package main

import (
    "fmt"
)

// Define una estructura DivideError
type DivideError struct {
    dividee int
    divider int
}

// Implementa la interfaz `error`
func (de *DivideError) Error() string {
    strFormat := `
    Cannot proceed, the divider is zero.
    dividee: %d
    divider: 0
`

    return fmt.Sprintf(strFormat, de.dividee)
}

// Define una función de división para el tipo `int`
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
    if varDivider == 0 {
            dData := DivideError{
                    dividee: varDividee,
                    divider: varDivider,
            }
            errorMsg = dData.Error()
            return
    } else {
            return varDividee / varDivider, ""
    }

}

func main() {

    // Caso normal
    if result, errorMsg := Divide(100, 10); errorMsg == "" {
            fmt.Println("100/10 = ", result)
    }
    // Cuando el divisor es cero, devuelve un mensaje de error
    if _, errorMsg := Divide(100, 0); errorMsg != "" {
            fmt.Println("errorMsg is: ", errorMsg)
    }

}

Al ejecutar el programa anterior, el resultado es:

100/10 =  10
errorMsg is:  
    Cannot proceed, the divider is zero.
    dividee: 100
    divider: 0


Usar errors.Is y errors.As

Desde Go 1.13,errorsel paquete errors introdujoerrors.Isyerrors.Aspara manejar cadenas de errores:

errors.Is

Comprueba si un error es un error específico o fue envuelto por dicho error.

Ejemplo

package main

import (
        "errors"
        "fmt"
)

var ErrNotFound = errors.New("not found")

func findItem(id int) error {
        return fmt.Errorf("database error: %w", ErrNotFound)
}

func main() {
        err := findItem(1)
        if errors.Is(err, ErrNotFound) {
                fmt.Println("Item not found")
        } else {
                fmt.Println("Other error:", err)
        }
}

errors.As

Convierte el error a un tipo específico para procesarlo posteriormente.

Ejemplo

package main

import (
        "errors"
        "fmt"
)

type MyError struct {
        Code int
        Msg  string
}

func (e *MyError) Error() string {
        return fmt.Sprintf("Code: %d, Msg: %s", e.Code, e.Msg)
}

func getError() error {
        return &MyError{Code: 404, Msg: "Not Found"}
}

func main() {
        err := getError()
        var myErr *MyError
        if errors.As(err, &myErr) {
                fmt.Printf("Custom error - Code: %d, Msg: %s\n", myErr.Code, myErr.Msg)
        }
}

panic y recover

El panic de Go se usa para manejar errores irrecuperables, y recover se usa para recuperarse de un panic.

panic:

  • Hace que el programa se bloquee e imprime información de la pila.
  • A menudo se usa cuando el programa no puede continuar ejecutándose.

recover:

  • Capturarpanic, evitar que el programa se bloquee.

Ejemplo

package main

import "fmt"

func safeFunction() {
        defer func() {
                if r := recover(); r != nil {
                        fmt.Println("Recovered from panic:", r)
                }
        }()
        panic("something went wrong")
}

func main() {
        fmt.Println("Starting program...")
        safeFunction()
        fmt.Println("Program continued after panic")
}

Al ejecutar el código anterior, el resultado es:

Starting program...
Recovered from panic: something went wrong
Program continued after panic
Otras extensiones