Go adopta una concepción relativamente diferente de los errores en comparación con lenguajes como Java, C++ y JavaScript y Python: en Go, el error es un valor del tipo integrado error, y la función normalmente lo devuelve al llamador junto con los demás valores de retorno. Así, comprobar y gestionar el error se convierte en una parte visible del flujo del programa, en lugar de trasladarse a un mecanismo de excepciones separado.
La guía de JetBrains, escrita originalmente por el colaborador de la comunidad Christoph Berger y posteriormente trasladada al blog de JetBrains Go, presenta un conjunto de técnicas y prácticas para gestionar errores de entrada y salida, redes, validación de datos y otros ámbitos. Se actualizó en agosto de 2026 para mantenerse al día con los cambios más recientes del lenguaje Go.
El retorno es el punto de partida
Cuando una función no tiene suficiente contexto para gestionar el problema, debe devolver el error a la función que la llamó. Go suele colocar el valor del error al final de la lista de retornos; una función como ReadFile() devuelve el contenido del archivo y un error cuyo valor es nil cuando tiene éxito, o un valor diferente cuando falla.
La guía explica que el llamador debe comprobar el error inmediatamente en lugar de ignorarlo. Si la función ha abierto un recurso, como un archivo o una conexión de red, debe utilizar defer para limpiarlo al salir, pero después de confirmar primero que la operación de apertura tuvo éxito, ya que intentar cerrar un recurso no válido puede provocar un problema adicional.
Añadir contexto sin dañar la estructura del error
El error puede pasar por una cadena de funciones antes de ser gestionado o registrado. Durante ese tránsito, cada función puede añadir información útil, como la operación que falló o la ruta que estaba procesando. Sin embargo, convertir el error en texto mediante la concatenación de err.Error() hace perder la cadena y la estructura tipada del error.
El enfoque correcto consiste en utilizar fmt.Errorf() con el verbo %w para envolver el error conservando el error original. Esto permite utilizar posteriormente errors.Unwrap() para acceder a una capa, o errors.Is() y errors.As() para comprobar errores específicos dentro de la cadena de envoltorios. Go 1.26 añade la función genérica errors.AsType() como alternativa con seguridad de tipos a As(); devuelve el error coincidente y un valor booleano, y permite que el compilador detecte errores de tipo que podrían aparecer en tiempo de ejecución con el enfoque anterior.
Errores múltiples y contexto cancelado
Los errores no siempre forman una cadena lineal. Al procesar, por ejemplo, un conjunto de archivos, algunas operaciones pueden tener éxito y otras fallar. El paquete estándar ofrece errors.Join(), disponible desde Go 1.20, para reunir varios errores en un único valor conservando el contenido procesado correctamente.
La guía advierte que errors.Unwrap() devuelve un único valor y, por ello, devuelve nil al trabajar con un error combinado. Para acceder a los errores agrupados, se debe comprobar que el valor implemente la interfaz que proporciona Unwrap() []error. Además, desde Go 1.20 se puede utilizar context.WithCancelCause() para vincular la cancelación del contexto a una causa personalizada y recuperar dicha causa mediante context.Cause(ctx), en lugar de limitarse al valor general context.Canceled.
¿Cuándo es apropiado utilizar panic?
panic y recover no deben ser un sustituto habitual de la comprobación de errores. Los errores previsibles, como las entradas de usuario no válidas, los archivos inexistentes o los tiempos de espera de red, deben gestionarse y devolverse mediante el flujo de retorno habitual.
El pánico resulta apropiado cuando el problema es inesperado y no existe una forma significativa de gestionarlo, como el fallo al compilar una expresión regular constante cuya validez debería haberse comprobado previamente. En servidores HTTP, la aplicación puede utilizar recover() dentro de una función diferida para impedir que el colapso del procesamiento de la solicitud actual afecte a las demás solicitudes, cuando sea posible. En cambio, situaciones como quedarse sin memoria pueden no dejar a la aplicación una opción práctica para continuar.
¿Qué es importante en la práctica para los desarrolladores?
- No ignores los errores: asignar el valor del error al identificador vacío o descartar el único valor de retorno puede retrasar la detección del problema y hacer más difícil diagnosticar sus efectos posteriores.
- Registra en el lugar adecuado: la función debe gestionar el error o devolverlo al llamador. La guía recomienda que las bibliotecas eviten registrar por cuenta propia, ya que sus usuarios pueden preferir registros o destinos de salida diferentes.
- Utiliza tipos adecuados: los tipos de error personalizados pueden contener información adicional, como fs.PathError, que proporciona la operación, la ruta y el error interno.
- Distingue los errores de red: net.OpError permite examinar la naturaleza del fallo de conexión, y un error temporal puede permitir reintentar o utilizar una estrategia como el retroceso exponencial.
- Aprovecha el número de bytes en las operaciones de entrada y salida: la interfaz io.Reader devuelve el número de bytes procesados junto con el error, lo que puede ayudar a reanudar la operación en lugar de volver a procesar todos los datos.
- Presta atención a io.EOF: este valor indica el final correcto de un flujo de lectura según la semántica del paquete, y no necesariamente un fallo que deba registrarse como un error ordinario.
- No utilices log.Fatal() sin considerar las consecuencias: llama a os.Exit(), lo que hace que se omitan las funciones diferidas; por ello, la guía propone limitar su uso o utilizar os.Exit() en la función main().
La conclusión editorial de certi.news es que la claridad del flujo de errores forma parte del diseño del programa, no es simplemente un estilo de escritura prolijo. Un envoltorio organizado, tipos adecuados y un registro responsable hacen que el diagnóstico sea práctico, mientras que depender de panic, de mensajes generales o de descartar errores oculta la información que el desarrollador necesitará más adelante. La decisión práctica sigue dependiendo del contexto de la aplicación: un servidor de solicitudes puede necesitar una recuperación limitada, mientras que un error irrecuperable puede requerir la terminación y el reinicio.