En Go, las funciones son bloques de código reutilizables que se definen con la palabra clave func. A diferencia de otros lenguajes donde los valores de retorno se limitan a uno solo, Go permite devolver múltiples valores de forma nativa. Esto no es un “hack” de sintaxis para simular punteros de salida (como los out-params en C); es una característica de primer orden diseñada para que el flujo de datos sea explícito y limpio.
Cuando defines una función, el nombre es crucial para la visibilidad: si el nombre comienza con una letra mayúscula, la función es exportada y será visible para cualquier otro paquete que importe el tuyo. Si comienza con minúscula, es no exportada (privada) y solo puede ser accedida dentro del mismo paquete.
La potencia real de las funciones en Go aparece cuando manejas errores. La convención estándar y obligatoria en la comunidad es que, si una función puede fallar, el último valor de retorno debe ser de tipo error. Esta estructura permite que las llamadas se sientan naturales: valor, err := funcion(). Si ignoras esta convención o decides no verificar el error, tu programa puede continuar operando con “valores cero” (como 0, "" o nil) que no representan datos reales, provocando comportamientos erráticos o panics difíciles de rastrear en producción.
package main
import (
"errors"
"fmt"
)
// User representa un perfil de usuario en nuestro sistema.
type User struct {
ID int
Name string
}
// GetUserByID es una función exportada (public) porque empieza con mayúscula.
// Retorna un puntero a User y un error. Esta es la firma estándar para
// operaciones que pueden fallar (como una consulta a base de datos).
func GetUserByID(id int) (*User, error) {
// Simulamos una validación interna de lógica de negocio.
if err := validateID(id); err != nil {
// Retornamos nil para el objeto y el error encontrado.
return nil, err
}
// Simulamos encontrar un usuario.
if id == 42 {
return &User{ID: 42, Name: "Alice"}, nil
}
return nil, errors.New("usuario no encontrado en la base de datos")
}
// validateID es una función no exportada (private) porque empieza con minúscula.
// Solo es visible dentro de este paquete.
func validateID(id int) error {
if id <= 0 {
return errors.New("el ID debe ser un entero positivo")
}
return nil
}
func main() {
// Caso 1: Operación exitosa.
user, err := GetUserByID(42)
if err != nil {
// En Go, siempre verificamos el error antes de usar el valor.
fmt.Printf("Error inesperado: %v\n", err)
return
}
fmt.Printf("Usuario encontrado: %+v\n", user)
// Caso 2: Operación con error de validación.
userErr, err := GetUserByID(-1)
if err != nil {
fmt.Printf("Error de validación: %v\n", err)
// userErr será nil en este caso.
_ = userErr
}
// Caso 3: Operación con error de búsqueda.
_, err = GetUserByID(99)
if err != nil {
fmt.Printf("Error de búsqueda: %v\n", err)
}
}
Análisis del código
Fíjate en GetUserByID. Su firma func GetUserByID(id int) (*User, error) es el patrón de diseño más común en Go. Al devolver (*User, error), el compilador nos permite usar la asignación corta := para capturar ambos valores simultáneamente.
Cuando la función falla (por ejemplo, si el ID es negativo), ejecutamos return nil, err. Es vital retornar nil como primer valor cuando el segundo es un error; esto le indica al llamante que el objeto que está intentando usar no es válido.
En validateID, vemos una función de soporte. Al empezar con minúscula, hemos encapsulado la lógica de validación para que no sea parte de la API pública del paquete, manteniendo la interfaz limpia. Finalmente, en main, observa cómo la estructura if err != nil se convierte en el centro del control de flujo: no tratamos al error como una excepción que interrumpe todo, sino como un valor más que el programa debe gestionar deliberadamente.
El error frecuente
Un error clásico, especialmente en etapas de desarrollo rápido, es ignorar el error usando el identificador en blanco _ cuando la función devuelve múltiples valores.
// ¡PELIGRO! user, _ := GetUserByID(-1) fmt.Println(user.Name) // Esto causará un panic: runtime error: invalid memory address or nil pointer dereference
En este caso, como GetUserByID(-1) devuelve nil para el puntero *User debido al error de validación, intentar acceder a user.Name dispara un panic porque estás intentando desreferenciar un puntero nulo. En Go, el error no es solo una notificación, es la señal para detener el uso de los otros valores devueltos.
N° 40