ProgramaciónDesarrollador Backend

¿Cómo está implementada la gestión de errores en Go, por qué Go eligió su modelo de manejo de errores y cuáles son las mejores prácticas para el uso de errores en la programación diaria?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

Historia del tema:

Go se destacó de muchos lenguajes por su enfoque en el manejo de errores, donde los errores son valores y no excepciones. Este diseño fue elegido por razones de transparencia y simplicidad: cada vez que algo puede salir mal, la función devuelve el error explícitamente como un segundo valor de retorno.

Problema:

Muchos principiantes intentan aplicar a Go los patrones habituales de try-catch, se pierden en la gran cantidad de chequeos de errores o no utilizan el envuelto de errores para transmitir contexto. Esto lleva a la pérdida de información y a la dificultad en la depuración.

Solución:

En Go, una función que puede devolver un error se declara de la siguiente manera:

func doSomething() (ResultType, error) { // ... if somethingWrong { return nil, errors.New("algo salió mal") } return result, nil }

La comprobación de errores es responsabilidad del lado que llama:

res, err := doSomething() if err != nil { log.Fatalf("proceso fallido: %w", err) }

Go 1.13 y versiones posteriores permiten "envolver" errores mediante fmt.Errorf("%w", err) para construir cadenas de errores. Esto mejora el diagnóstico y promueve la buena práctica de la descripción contextual de errores.

Características clave:

  • Los errores son valores devueltos por funciones (no hay try/catch)
  • Se pueden crear tipos de errores personalizados (utilizando la interfaz error)
  • El envoltura de errores hace que la depuración y el registro sean más transparentes

Preguntas trampa.

¿Por qué crear tus propios tipos de errores si puedes simplemente devolver errors.New("...")?

La respuesta correcta: los tipos de errores personalizados permiten no solo comunicar el texto del error, sino también conservar información adicional para un posterior procesamiento (por ejemplo, códigos de retorno, contexto), así como implementar un manejo más detallado a través de la afirmación de tipo.

Ejemplo:

type NotFoundError struct { Resource string } func (e NotFoundError) Error() string { return fmt.Sprintf("%s no encontrado", e.Resource) } // Comprobación if _, ok := err.(NotFoundError); ok { // tratamiento del error de búsqueda }

¿Es el panic un buen sustituto de error para errores críticos?

No, panic en Go se utiliza solo en situaciones realmente desesperadas, por ejemplo, ante errores en la propia programación (invariantes de programación), pero no para fallos ordinarios (por ejemplo, si no se pudo abrir un archivo). Usar panic para señalar errores rutinarios es un mal y conduce a un código ilegible e incontrolable.

¿Qué sucede si se omite el manejo del error (err) en funciones anidadas?

El error se "pierde", el código continúa ejecutándose, lo que puede llevar a la propagación de un estado erróneo. Siempre es importante manejar correctamente cada retorno de error.

Errores típicos y anti-patrones

  • Ignorar errores devueltos usando _ = ...
  • Usar panic/recover en lugar de error para el control de flujo
  • Redirigir errores no enviados sin un mensaje de contexto

Ejemplo de la vida real

Caso negativo

Cada función simplemente devuelve errors.New(...), los errores no están envueltos, los tipos de errores son diversos, pero el manejo siempre es el mismo: se registra y se lanza. Como resultado, los archivos de log están llenos de mensajes no informativos, imposibles de rastrear hasta la causa original.

Pros:

  • Escritura rápida de código
  • Menos código para el manejo

Contras:

  • Mala trazabilidad
  • Sin posibilidad de filtrar o diferenciar errores por tipo

Caso positivo

Se utilizan envolturas de errores a través de fmt.Errorf("%w", err), errores personalizados con campos útiles, verificaciones a través de errors.Is()/errors.As(). Gracias a esto, se puede registrar con detalles, separar errores de negocio de fallos de entorno y escribir una lógica de reintento confiable.

Pros:

  • Depuración conveniente
  • Código más limpio
  • Manejo de errores flexible

Contras:

  • Más código y lógica de plantilla
  • Necesidad de mantener tipos de errores