ProgramaciónDesarrollador Go

¿Cuáles son las características del manejo de errores (errors) en Go, cómo crear y manejar errores correctamente, y cómo implementar tipos de errores personalizados?

Supere entrevistas con el asistente de IA Hintsage

Respuesta

Go fue diseñado desde el principio para devolver errores de manera explícita en lugar de usar excepciones. Esto permite escribir código predecible y evitar trampas ocultas propias de las construcciones try/catch de otros lenguajes (por ejemplo, Java o C++). Un error en Go es una interfaz que implementa el método Error(), lo que permite crear errores tanto simples como complejos/envueltos con contexto.

Los problemas surgen cuando los errores se manejan incorrectamente (por ejemplo, si se ignoran a través de _ o no se envuelven con información adicional para depuración) o al crear "errores mágicos" que no cumplen con la interfaz error. También es importante devolver errores desde funciones públicas y verificarlos en cada llamada.

La solución es utilizar enfoques estándar:

  • Siempre devolver un error como el último valor de la función.
  • Usar errors.New, fmt.Errorf para crear errores.
  • Para errores personalizados, crear sus propios tipos que implementen Error().
  • Para casos complejos, aplicar errores-envolturas (errors.Wrap) para no perder el contexto.

Ejemplo de código:

import ( "errors" "fmt" ) type NotFoundError struct { Resource string } func (e *NotFoundError) Error() string { return fmt.Sprintf("%s no encontrado", e.Resource) } func GetUser(id int) (string, error) { if id != 1 { return "", &NotFoundError{"Usuario"} } return "Steve", nil } func main() { user, err := GetUser(2) if err != nil { if nfe, ok := err.(*NotFoundError); ok { fmt.Println(nfe.Resource, "problema") } else { fmt.Println("error:", err) } } fmt.Println("usuario:", user) }

Características clave:

  • Los errores siempre se devuelven explícitamente, generalmente como el último argumento de la función.
  • Para errores complejos, se pueden definir sus propios tipos (struct) que implementen la interfaz error.
  • Para envolver errores y mantener el stack trace, se puede usar el paquete "errors" (Go 1.13+ admite errors.Is y errors.As para trabajar con cadenas de errores).

Preguntas capciosas.

¿Se puede hacer una comparación de error mediante ==, o se deben usar métodos especiales?

Es mejor usar siempre errors.Is() para comparar con errores-envoltura, de lo contrario, la comparación mediante == puede no funcionar si hay un envoltorio de error.

if errors.Is(err, os.ErrNotExist) { // manejo de la falta de archivo }

¿Es obligatorio implementar un tipo-struct para un error personalizado, o es suficiente con usar errors.New("...")?

Si solo se necesita el texto del error, es suficiente con errors.New(). Si es importante mantener el contexto (por ejemplo, el nombre del recurso), es mejor definir un struct con un método Error().

¿Cómo devolver correctamente un error en caso de éxito en la ejecución de la función?

En caso de éxito, siempre devuelva un error nil.

return result, nil

Errores típicos y anti-patrones

  • Ignorar errores devueltos (a través de _ o omitir el manejo)
  • Comparar errores solo por ==, a pesar de los errores anidados
  • Cadenas fijas en lugar de errores
  • Falta de contexto adicional en los errores

Ejemplo de la vida real

Caso negativo

Un desarrollador escribe una función que devuelve un error sin explicaciones (simplemente errors.New("fallo")). Al analizar los registros, no se puede establecer la causa.

Ventajas:

  • Rapidez en la implementación

Desventajas:

  • Errores poco informativos
  • Dificultad para depurar

Caso positivo

Un desarrollador define un tipo de error personalizado NotFoundError y lo devuelve con una descripción detallada.

Ventajas:

  • Comodidad en la filtración de registros
  • Facilidad para encontrar y manejar errores

Desventajas:

  • Necesidad de describir estructuras adicionales