> serie: aprende go de 0 a 100 | artículo 9 de 19
Todo dev que llega a Go pasa por las mismas dos fases: primero «¿en serio tengo que escribir if err != nil cada tres líneas?» y después, unos meses más tarde, «vale, ahora entiendo por qué». Este artículo sobre manejo de errores existe para acortar el viaje entre ambas.
Los errores son valores
En Go no hay excepciones que vuelan por los aires. El manejo de errores trata un error como un valor normal que implementa una interfaz de un método (Error handling and Go):
// error es solo una interfaz de la stdlib:
// type error interface { Error() string }
f, err := os.Open("config.json")
if err != nil {
// gestiona y corta pronto: happy path a la izquierda
return fmt.Errorf("abriendo config: %w", err)
}
defer f.Close()
La consecuencia filosófica es enorme: el error es parte de la firma de la función, visible en cada llamada, imposible de ignorar por accidente. En Java el throws se esquiva, en Python el except se olvida y en JS la promesa rechazada se pierde en el limbo. En Go el error te mira a la cara desde el valor de retorno (FAQ, exceptions).
El patrón visual resultante se llama happy path a la izquierda: los errores se gestionan y cortan pronto con return, y el flujo feliz baja recto sin indentación.
Envolver errores: %w, errors.Is y errors.As
Desde Go 1.13 los errores se encadenan (Working with Errors in Go 1.13 y paquete errors):
// %w envuelve: el error original sigue dentro.
err := fmt.Errorf("consultando cliente: %w", sql.ErrNoRows)
// errors.Is pregunta por un error concreto en la cadena.
if errors.Is(err, sql.ErrNoRows) {
fmt.Println("cliente no encontrado, no es grave")
}
// errors.As busca un TIPO en la cadena y lo extrae.
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Println("falló la ruta:", pathErr.Path)
}
El verbo %w de fmt.Errorf añade contexto sin destruir el error original. Cada capa de tu aplicación aporta su miga y errors.Is puede seguir preguntando por la causa raíz al fondo de la cadena. Compáralo con las stack traces de Java. Aquí el contexto lo eliges tú, capa a capa, y se lee como una frase.

Errores centinela y errores con datos
Dos formas de crear errores propios, cada una con su caso de uso:
// Error centinela: variable exportada para comparar.
var ErrSaldoInsuficiente = errors.New("saldo insuficiente")
// Error con datos: un struct que implementa Error().
type ErrorValidacion struct {
Campo string
Valor any
}
func (e *ErrorValidacion) Error() string {
return fmt.Sprintf("campo %q inválido: %v", e.Campo, e.Valor)
}
El centinela (ErrAlgo exportado, por convención con prefijo Err) sirve para condiciones conocidas que el llamante querrá distinguir con errors.Is.
El struct sirve cuando el error lleva carga útil y se pesca con errors.As. Y recuerda el gotcha del artículo 7: devuelve siempre error como tipo, nunca un puntero concreto.
panic y recover: la salida de emergencia
Sí, existe panic. No, no es tu try/catch (Effective Go, panic):
func procesa() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recuperado:", r)
}
}()
panic("algo imposible acaba de pasar")
}
La convención de la comunidad y de Effective Go es clara: panic se reserva para lo irrecuperable (corrupción interna, imposibles lógicos) y recover para no tumbar el proceso entero desde una goroutine, como hace el servidor HTTP de la stdlib. Errores esperables (archivo que no existe, entrada inválida, red caída) van SIEMPRE como valores. Si usas panic para controlar flujo, un gopher llora en algún lugar del mundo.
Reto de la semana: pon a prueba el manejo de errores
- Escribe ParseaEdad(s string) (int, error) que envuelva con %w el error de strconv.Atoi y rechace edades fuera de 0-130 con un error propio.
- Crea el centinela ErrNoEncontrado en una función de búsqueda y distínguelo en el llamante con errors.Is.
- Define ErrorValidacion con campo y valor, lánzalo envuelto en dos capas de contexto y extráelo con errors.As.
- Bonus: provoca un panic con un índice fuera de rango, atrápalo con recover en un defer y convierte el susto en un error normal.
Manejo de errores: nivel completado.
En el próximo artículo: punteros. Qué son, cuándo usarlos y por qué en Go no dan el miedo que daban en C.
> exit