CAP 07 · LEC 01·Manejo de errores

Errores como valor: el patrón `return err`

En Go los errores no se lanzan: se devuelven. Cada función que puede fallar retorna un `error` como último valor, y el código que llama decide qué hacer. Es explícito, predecible y sin magia.

● INTERMEDIO7 min lecturapor Fernando Herrera · actualizado mayo de 2026
¿Encontraste un error o algo que mejorar?Editá esta lección en GitHub →

La interfaz `error`

error es una interfaz built-in con un solo método. Cualquier tipo que implemente Error() string satisface la interfaz, y por eso los errores en Go son valores normales: se asignan, se comparan, se pasan a funciones, se guardan en structs.

package main import "fmt" // La interfaz error definida en el paquete builtin: // // type error interface { // Error() string // } // // Cualquier tipo con un método Error() string es un error. func main() { var err error = fmt.Errorf("algo salió mal") fmt.Println(err) // algo salió mal fmt.Println(err.Error()) // algo salió mal // Un error es nil cuando no hay error var sinError error fmt.Println(sinError == nil) // true }
Salidaalgo salió mal algo salió mal true

Retornar `(T, error)`

La convención canónica en Go: las funciones que pueden fallar retornan dos valores. Primero el resultado, después el error. Si err != nil, el resultado puede estar en estado cero y no debe usarse.

package main import ( "errors" "fmt" ) func divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("división por cero") } return a / b, nil } func main() { // Caso correcto result, err := divide(10, 2) if err != nil { fmt.Println("error:", err) return } fmt.Println("resultado:", result) // Caso con error _, err = divide(10, 0) if err != nil { fmt.Println("error:", err) } }
Salidaresultado: 5 error: división por cero

El idiom `if err != nil`

El patrón más visible de Go: tras cada llamada que puede fallar, verificas el error y decides. Lo más común es propagarlo hacia arriba con return err. Cada chequeo es una decisión consciente sobre el flujo de control.

package main import ( "errors" "fmt" "strconv" ) func parseAge(input string) (int, error) { age, err := strconv.Atoi(input) if err != nil { return 0, err // propagamos el error al caller } if age < 0 { return 0, errors.New("edad no puede ser negativa") } return age, nil } func greet(input string) (string, error) { age, err := parseAge(input) if err != nil { return "", err } return fmt.Sprintf("hola, tienes %d años", age), nil } func main() { msg, err := greet("42") if err != nil { fmt.Println("error:", err) return } fmt.Println(msg) _, err = greet("doce") if err != nil { fmt.Println("error:", err) } }
Salidahola, tienes 42 años error: strconv.Atoi: parsing "doce": invalid syntax

`errors.New` vs `fmt.Errorf`

Hay dos formas estándar de crear un error. errors.New para mensajes estáticos. fmt.Errorf cuando necesitas interpolar valores con verbos de formato.

package main import ( "errors" "fmt" ) func findUser(id int) (string, error) { // Mensaje estático: errors.New if id == 0 { return "", errors.New("id no puede ser cero") } // Mensaje con contexto: fmt.Errorf if id < 0 { return "", fmt.Errorf("id inválido: %d", id) } if id > 1000 { return "", fmt.Errorf("usuario %d no encontrado en tabla users", id) } return fmt.Sprintf("user-%d", id), nil } func main() { for _, id := range []int{42, 0, -3, 9999} { name, err := findUser(id) if err != nil { fmt.Println("error:", err) continue } fmt.Println("usuario:", name) } }
Salidausuario: user-42 error: id no puede ser cero error: id inválido: -3 error: usuario 9999 no encontrado en tabla users

No uses `panic` para errores esperados

panic aborta el programa (a menos que un recover lo atrape). En Go se reserva para errores irrecuperables del programador: accesos nil, índices fuera de rango, invariantes rotos. Para errores normales del dominio o de I/O, siempre retorna error.

package main import ( "errors" "fmt" ) // ❌ Mal: usar panic para un error esperado func withdrawBad(balance, amount float64) float64 { if amount > balance { panic("fondos insuficientes") // aborta todo el programa } return balance - amount } // ✅ Bien: retornar el error y dejar que el caller decida func withdraw(balance, amount float64) (float64, error) { if amount > balance { return 0, errors.New("fondos insuficientes") } return balance - amount, nil } func main() { newBalance, err := withdraw(100, 150) if err != nil { fmt.Println("rechazado:", err) return } fmt.Println("nuevo saldo:", newBalance) }
Salidarechazado: fondos insuficientes
¿Cuándo sí usar panic?

Solo para condiciones que indican un bug en el código: un caso default imposible en un switch, un puntero que no debería ser nil, una invariante de programa que se rompió. Nunca para validación de input ni para I/O fallido.