ProgramaciónDesarrollador Backend

Hable sobre el trabajo con errores a través del paquete de envoltura de errores en Go: ¿qué es la envoltura de errores, por qué es importante y cómo implementar correctamente la anidación de errores?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

Historia del tema:

Antes de Go 1.13, el error era una simple interfaz. Para proporcionar contexto adicional a los errores, a menudo se creaban tipos personalizados, pero no había un mecanismo estructurado para la anidación, como en Java moderno o C#. Con la llegada de Go 1.13, se introdujo un método estándar para la envoltura (wrapping) de errores a través de la función fmt.Errorf y la detección de la causa raíz (errors.Is/errors.As).

Problema:

En aplicaciones complejas, donde un error puede ocurrir en diferentes niveles de la pila, es importante no perder el contexto al devolver un error desde las profundidades del código. De lo contrario, la depuración se vuelve difícil y no se puede entender dónde ocurrió la falla en la cadena.

Solución:

Go propone envolver errores en nuevos objetos de error que contienen la causa. Para esto se utiliza %w en fmt.Errorf, y para verificar las causas subyacentes se utilizan errors.Is o errors.As del paquete errors.

Ejemplo de código:

import ( "errors" "fmt" ) var ErrNotFound = errors.New("no encontrado") func getData() error { return fmt.Errorf("base de datos de servicio: %w", ErrNotFound) } func main() { err := getData() if errors.Is(err, ErrNotFound) { fmt.Println("Causa detectada: no encontrado") } else { fmt.Println("Otro error:", err) } }

Características clave:

  • Uso de %w para anidar errores a través de fmt.Errorf.
  • Detección de causas anidadas de errores a través de errors.Is y errors.As.
  • Compatibilidad con tipos de error personalizados al anidar y desempaquetar.

Preguntas capciosas.

¿Qué operador de formato se necesita para envolver errores a través de fmt.Errorf?

Es correcto usar %w, no %v — solo %w proporciona soporte para desempaquetar.

Ejemplo de código:

fmt.Errorf("error: %w", err)

¿Se pueden crear manualmente cadenas de errores sin fmt.Errorf y aún así detectarlas a través de errors.Is?

No, es necesario implementar la interfaz Unwrap, de lo contrario las funciones estándar no desenredan la cadena.

Ejemplo de código de la interfaz:

type wrappedError struct { msg string err error } func (w wrappedError) Error() string { return w.msg + ": " + w.err.Error() } func (w wrappedError) Unwrap() error { return w.err }

¿Devuelven los errores valores nil después de la envoltura, si el error anidado era nil?

No, si el error anidado es nil, el error envuelto también será nil solo con un uso correcto. Pero si se crea manualmente una estructura envoltora donde el campo de error es nil, no será nil. Esto a menudo causa confusión al verificar.

Errores comunes y antipatrones

  • No usar %w, sino solo %v — se pierde la capacidad de analizar la cadena.
  • No implementar Unwrap() para sus estructuras de error.
  • No realizar la verificación a través de errors.Is/As, sino solo comparar el error directamente.
  • Crear nuevos errores sin causa (pérdida de contexto).

Ejemplo de la vida real

Caso negativo

En la aplicación simplemente se devolvía un nuevo error con un mensaje en cada nivel:

return errors.New("error al escribir en la base de datos")

Pros:

  • Simple y rápido.

Contras:

  • No se puede distinguir que el error es realmente "no encontrado" desde las profundidades, y se rompe la lógica del negocio arriba.
  • Pérdida de información sobre la pila y la fuente del error.

Caso positivo

Se utilizó la envoltura de errores con %w y el análisis a través de errors.Is:

if errors.Is(err, ErrNotFound) { return fmt.Errorf("error en el nivel del servicio: %w", err) }

Pros:

  • Se define correctamente la causa en cualquier nivel.
  • Más fácil de depurar, siempre se ve el contexto original.

Contras:

  • Requiere entender cómo funciona la envoltura y escritura calificada de errores.