Curso Go #4: Funciones y Manejo de Errores en Go
Introduccion: funciones y errores en Go
Bienvenido a la Parte 4 del curso de Go para principiantes. En el articulo anterior aprendiste a controlar el flujo de tu programa con if, switch y for. Ahora vamos a aprender a organizar tu codigo en funciones reutilizables y a manejar errores de la forma idiomatica de Go.

Las funciones son los bloques fundamentales de cualquier programa en Go. Pero lo que hace a Go realmente especial es su enfoque unico para manejar errores: en lugar de excepciones, Go usa retornos multiples y el patron if err != nil. Este enfoque es explicito, predecible y te obliga a pensar en que puede salir mal en cada paso.
Sintaxis basica de funciones
Una funcion en Go se declara con la palabra clave func, seguida del nombre, los parametros entre parentesis y el tipo de retorno.
1package main
2
3import "fmt"
4
5// Funcion sin retorno
6func saludar(nombre string) {
7 fmt.Println("Hola,", nombre)
8}
9
10// Funcion con retorno
11func sumar(a int, b int) int {
12 return a + b
13}
14
15// Parametros del mismo tipo se pueden abreviar
16func multiplicar(a, b int) int {
17 return a * b
18}
19
20func main() {
21 saludar("Carlos")
22 resultado := sumar(5, 3)
23 fmt.Println("5 + 3 =", resultado)
24 fmt.Println("4 * 7 =", multiplicar(4, 7))
25}
Retornos multiples: la firma de Go
Una de las caracteristicas mas distintivas de Go es que las funciones pueden retornar multiples valores. Esto es fundamental para el manejo de errores.
1package main
2
3import (
4 "errors"
5 "fmt"
6)
7
8// Retorna dos valores: resultado y error
9func dividir(a, b float64) (float64, error) {
10 if b == 0 {
11 return 0, errors.New("no se puede dividir entre cero")
12 }
13 return a / b, nil
14}
15
16func main() {
17 resultado, err := dividir(10, 3)
18 if err != nil {
19 fmt.Println("Error:", err)
20 return
21 }
22 fmt.Printf("10 / 3 = %.2f
23", resultado)
24
25 resultado, err = dividir(10, 0)
26 if err != nil {
27 fmt.Println("Error:", err)
28 return
29 }
30 fmt.Printf("10 / 0 = %.2f
31", resultado)
32}
Otro ejemplo practico: una funcion que busca un elemento en un slice y retorna su posicion.
1package main
2
3import "fmt"
4
5func buscar(nombres []string, objetivo string) (int, bool) {
6 for i, nombre := range nombres {
7 if nombre == objetivo {
8 return i, true
9 }
10 }
11 return -1, false
12}
13
14func main() {
15 lista := []string{"Ana", "Pedro", "Maria", "Luis"}
16
17 if pos, encontrado := buscar(lista, "Maria"); encontrado {
18 fmt.Printf("Maria esta en la posicion %d
19", pos)
20 } else {
21 fmt.Println("Maria no fue encontrada")
22 }
23}
_ para descartarlo: _, err := dividir(10, 0).
Retornos con nombre
Go permite nombrar los valores de retorno en la firma de la funcion. Esto los declara como variables locales y permite usar return sin argumentos (naked return).
1package main
2
3import "fmt"
4
5func calcularEstadisticas(numeros []float64) (suma, promedio float64, cantidad int) {
6 cantidad = len(numeros)
7 if cantidad == 0 {
8 return // retorna los valores cero de cada tipo
9 }
10
11 for _, n := range numeros {
12 suma += n
13 }
14 promedio = suma / float64(cantidad)
15 return // retorna suma, promedio, cantidad
16}
17
18func main() {
19 datos := []float64{85.5, 92.0, 78.3, 95.1, 88.7}
20 suma, promedio, cantidad := calcularEstadisticas(datos)
21
22 fmt.Printf("Cantidad: %d
23", cantidad)
24 fmt.Printf("Suma: %.2f
25", suma)
26 fmt.Printf("Promedio: %.2f
27", promedio)
28}
Funciones variadicas
Una funcion variadica acepta un numero variable de argumentos del mismo tipo. Se declara con ... antes del tipo del ultimo parametro.
1package main
2
3import "fmt"
4
5func sumarTodos(numeros ...int) int {
6 total := 0
7 for _, n := range numeros {
8 total += n
9 }
10 return total
11}
12
13func imprimirLinea(separador string, valores ...string) {
14 for i, v := range valores {
15 if i > 0 {
16 fmt.Print(separador)
17 }
18 fmt.Print(v)
19 }
20 fmt.Println()
21}
22
23func main() {
24 fmt.Println(sumarTodos(1, 2, 3)) // 6
25 fmt.Println(sumarTodos(10, 20, 30, 40)) // 100
26
27 // Pasar un slice a una funcion variadica
28 nums := []int{5, 10, 15}
29 fmt.Println(sumarTodos(nums...)) // 30
30
31 imprimirLinea(" - ", "Go", "es", "genial") // Go - es - genial
32}
fmt.Println que has usado a lo largo del curso es, de hecho, una funcion variadica. Su firma es func Println(a ...any) (n int, err error).
Funciones anonimas y closures
Go soporta funciones anonimas (funciones sin nombre) que pueden asignarse a variables o ejecutarse inmediatamente. Cuando una funcion anonima captura variables de su entorno, se convierte en un closure.
1package main
2
3import "fmt"
4
5func main() {
6 // Funcion anonima asignada a una variable
7 doble := func(n int) int {
8 return n * 2
9 }
10 fmt.Println(doble(5)) // 10
11
12 // Funcion anonima ejecutada inmediatamente (IIFE)
13 resultado := func(a, b int) int {
14 return a + b
15 }(3, 7)
16 fmt.Println(resultado) // 10
17
18 // Closure: captura la variable contador
19 contador := 0
20 incrementar := func() int {
21 contador++
22 return contador
23 }
24
25 fmt.Println(incrementar()) // 1
26 fmt.Println(incrementar()) // 2
27 fmt.Println(incrementar()) // 3
28 fmt.Println("Contador final:", contador) // 3
29}
Un ejemplo practico de closures: un generador de IDs.
1package main
2
3import "fmt"
4
5func crearGeneradorID(prefijo string) func() string {
6 id := 0
7 return func() string {
8 id++
9 return fmt.Sprintf("%s-%04d", prefijo, id)
10 }
11}
12
13func main() {
14 genUsuario := crearGeneradorID("USR")
15 genPedido := crearGeneradorID("ORD")
16
17 fmt.Println(genUsuario()) // USR-0001
18 fmt.Println(genUsuario()) // USR-0002
19 fmt.Println(genPedido()) // ORD-0001
20 fmt.Println(genUsuario()) // USR-0003
21 fmt.Println(genPedido()) // ORD-0002
22}
defer, panic y recover
Go tiene tres mecanismos especiales para controlar el flujo en situaciones excepcionales.
defer pospone la ejecucion de una funcion hasta que la funcion que la contiene termine. Es ideal para liberar recursos.
1package main
2
3import (
4 "fmt"
5 "os"
6)
7
8func leerArchivo(nombre string) {
9 archivo, err := os.Open(nombre)
10 if err != nil {
11 fmt.Println("Error al abrir:", err)
12 return
13 }
14 defer archivo.Close() // Se ejecuta al salir de la funcion
15
16 fmt.Println("Archivo abierto correctamente")
17 // ... procesar el archivo ...
18 // archivo.Close() se llama automaticamente al final
19}
20
21func main() {
22 // Los defers se ejecutan en orden LIFO (ultimo en entrar, primero en salir)
23 defer fmt.Println("1 - Primero en defer, ultimo en ejecutar")
24 defer fmt.Println("2 - Segundo en defer")
25 defer fmt.Println("3 - Tercero en defer, primero en ejecutar")
26
27 fmt.Println("Codigo principal")
28}
1Codigo principal
23 - Tercero en defer, primero en ejecutar
32 - Segundo en defer
41 - Primero en defer, ultimo en ejecutar
panic detiene la ejecucion normal del programa. recover permite recuperarse de un panic dentro de un defer.
1package main
2
3import "fmt"
4
5func operacionRiesgosa() {
6 defer func() {
7 if r := recover(); r != nil {
8 fmt.Println("Recuperado del panic:", r)
9 }
10 }()
11
12 fmt.Println("Iniciando operacion...")
13 panic("algo salio terriblemente mal")
14 fmt.Println("Esta linea nunca se ejecuta")
15}
16
17func main() {
18 operacionRiesgosa()
19 fmt.Println("El programa continua despues del panic recuperado")
20}
panic y recover NO son el equivalente de try/catch de otros lenguajes. Solo debes usar panic para errores verdaderamente irrecuperables (como un estado corrupto del programa). Para errores normales, siempre usa el patron de retorno de errores.
El patron if err != nil: manejo de errores en Go
El manejo de errores en Go es explicito y se basa en retornar valores de tipo error. Este es el patron mas importante que debes dominar.
1package main
2
3import (
4 "errors"
5 "fmt"
6 "strconv"
7)
8
9func convertirAEdad(texto string) (int, error) {
10 edad, err := strconv.Atoi(texto)
11 if err != nil {
12 return 0, fmt.Errorf("valor invalido '%s': %w", texto, err)
13 }
14 if edad < 0 || edad > 150 {
15 return 0, fmt.Errorf("edad fuera de rango: %d", edad)
16 }
17 return edad, nil
18}
19
20func main() {
21 entradas := []string{"25", "abc", "-5", "200", "42"}
22
23 for _, entrada := range entradas {
24 edad, err := convertirAEdad(entrada)
25 if err != nil {
26 fmt.Printf("Error con '%s': %s
27", entrada, err)
28 continue
29 }
30 fmt.Printf("Edad valida: %d
31", edad)
32 }
33}
errors.New crea errores simples. fmt.Errorf crea errores con formato. El verbo %w permite envolver (wrap) el error original para mantener la cadena de errores.
1package main
2
3import (
4 "errors"
5 "fmt"
6)
7
8// Errores personalizados como variables de paquete
9var (
10 ErrNoEncontrado = errors.New("recurso no encontrado")
11 ErrNoAutorizado = errors.New("no autorizado")
12 ErrSinConexion = errors.New("sin conexion a la base de datos")
13)
14
15func buscarUsuario(id int) (string, error) {
16 if id <= 0 {
17 return "", fmt.Errorf("buscarUsuario: id invalido %d: %w", id, ErrNoEncontrado)
18 }
19 // Simulacion: solo existe el usuario con id 1
20 if id != 1 {
21 return "", fmt.Errorf("buscarUsuario: id %d: %w", id, ErrNoEncontrado)
22 }
23 return "Carlos Gomez", nil
24}
25
26func main() {
27 nombre, err := buscarUsuario(99)
28 if err != nil {
29 // errors.Is verifica si el error (o alguno envuelto) es el esperado
30 if errors.Is(err, ErrNoEncontrado) {
31 fmt.Println("El usuario no existe:", err)
32 } else {
33 fmt.Println("Error inesperado:", err)
34 }
35 return
36 }
37 fmt.Println("Usuario encontrado:", nombre)
38}
Tipos de error personalizados
Para errores mas complejos, puedes crear tipos que implementen la interfaz error (que solo requiere el metodo Error() string).
1package main
2
3import (
4 "errors"
5 "fmt"
6)
7
8// Tipo de error personalizado
9type ErrorValidacion struct {
10 Campo string
11 Mensaje string
12}
13
14func (e *ErrorValidacion) Error() string {
15 return fmt.Sprintf("validacion fallida en '%s': %s", e.Campo, e.Mensaje)
16}
17
18func validarUsuario(nombre string, edad int) error {
19 if nombre == "" {
20 return &ErrorValidacion{Campo: "nombre", Mensaje: "no puede estar vacio"}
21 }
22 if edad < 0 || edad > 150 {
23 return &ErrorValidacion{Campo: "edad", Mensaje: "debe estar entre 0 y 150"}
24 }
25 return nil
26}
27
28func main() {
29 err := validarUsuario("", 25)
30 if err != nil {
31 // errors.As extrae el tipo concreto del error
32 var errVal *ErrorValidacion
33 if errors.As(err, &errVal) {
34 fmt.Printf("Campo con error: %s
35", errVal.Campo)
36 fmt.Printf("Detalle: %s
37", errVal.Mensaje)
38 }
39 return
40 }
41
42 err = validarUsuario("Ana", -5)
43 if err != nil {
44 fmt.Println(err)
45 }
46}
errors.Is para comparar errores por valor (como los errores centinela ErrNoEncontrado) y errors.As para comparar por tipo (como *ErrorValidacion). Ambas funciones recorren la cadena de errores envueltos automaticamente.
Buenas practicas para manejo de errores
El manejo de errores en Go tiene convenciones claras que debes seguir:
- Siempre verifica los errores: nunca ignores un valor de retorno de tipo
error. - Agrega contexto: usa
fmt.Errorfcon%wpara envolver errores con informacion adicional. - Errores centinela: define errores conocidos como variables de paquete para que los consumidores puedan usar
errors.Is. - No uses panic para errores normales: reservalo para situaciones verdaderamente irrecuperables.
- Retorna errores, no los imprimas: la funcion que llama decide que hacer con el error.
1// MAL: imprimir y continuar
2func procesar(datos string) int {
3 resultado, err := convertir(datos)
4 if err != nil {
5 fmt.Println("Error:", err) // Quien llama no sabe que fallo
6 return 0
7 }
8 return resultado
9}
10
11// BIEN: retornar el error con contexto
12func procesar(datos string) (int, error) {
13 resultado, err := convertir(datos)
14 if err != nil {
15 return 0, fmt.Errorf("procesar '%s': %w", datos, err)
16 }
17 return resultado, nil
18}
Resumen y proximo articulo
En este articulo aprendiste los pilares de la programacion en Go:
- Funciones: sintaxis basica, parametros abreviados y exportacion con mayusculas
- Retornos multiples: la caracteristica mas distintiva de Go, esencial para el manejo de errores
- Retornos con nombre: utiles para documentar y en funciones cortas
- Funciones variadicas: aceptan un numero variable de argumentos con
... - Funciones anonimas y closures: capturan variables del entorno para crear generadores y callbacks
- defer, panic, recover: para liberar recursos y manejar situaciones criticas
- Patron if err != nil: el manejo explicito de errores que define a Go
- Errores personalizados: tipos que implementan la interfaz
errorconerrors.Isyerrors.As
En el proximo articulo (Parte 5) aprenderemos sobre structs e interfaces: como definir tipos compuestos, asociarles metodos y usar interfaces para escribir codigo flexible y desacoplado.
Comments
Sign in to leave a comment
No comments yet. Be the first!