Guía Práctica de Go (Golang): Concurrencia, Structs y Manejo de Errores
Go se ha consolidado como el estándar de facto para microservicios, herramientas de infraestructura (Docker, Kubernetes, Terraform) y software de alta concurrencia. Su diseño prioriza la simplicidad, la velocidad de compilación y la ausencia de magia innecesaria.
Esta guía resume los patrones y conceptos fundamentales que necesitas dominar para escribir Go idiomático.
1. Comandos Esenciales de Go CLI
La herramienta oficial de Go incluye todo lo necesario para inicializar, compilar y verificar dependencias sin herramientas externas:
Inicializar un nuevo módulo:
bash go mod init github.com/tu-usuario/mi-proyectoDescargar paquetes y limpiar dependencias no usadas:
bash go mod tidyEjecutar en desarrollo directamente:
bash go run main.goCompilar un binario estático optimizado para producción:
bash CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server main.go
2. Tipos, Structs y Composición (Sin Clases)
Go no tiene clases ni herencia tradicional; utiliza structs y composición.
package main
import "fmt"
type Usuario struct {
ID int `json:"id"`
Nombre string `json:"nombre"`
Activo bool `json:"activo"`
}
// Método con receiver por valor o puntero (usa puntero para mutar)
func (u *Usuario) Desactivar() {
u.Activo = false
}
func main() {
user := Usuario{ID: 1, Nombre: "Nico", Activo: true}
user.Desactivar()
fmt.Printf("%+v
", user) // {ID:1 Nombre:Nico Activo:false}
}
3. Manejo de Errores Idiomático (Explicit Over Magic)
Go no implementa excepciones tipo try/catch. Las funciones retornan valores y errores explícitos como tuplas:
package main
import (
"errors"
"fmt"
)
var ErrSaldoInsuficiente = errors.New("saldo insuficiente para la operación")
func Retirar(saldo float64, monto float64) (float64, error) {
if monto > saldo {
return saldo, ErrSaldoInsuficiente
}
return saldo - monto, nil
}
func main() {
nuevoSaldo, err := Retirar(100, 150)
if err != nil {
if errors.Is(err, ErrSaldoInsuficiente) {
fmt.Println("Alerta:", err)
}
return
}
fmt.Println("Nuevo saldo:", nuevoSaldo)
}
4. Concurrencia Práctica: Goroutines y Channels
Las goroutines son hilos ligeros administrados por el runtime de Go (consumen apenas ~2KB de stack inicial).
Lanzar tareas concurrentes con WaitGroup
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d iniciando...
", id)
time.Sleep(time.Millisecond * 500)
fmt.Printf("Worker %d finalizado.
", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("Todas las tareas terminaron.")
}
Comunicación segura mediante Channels
"No te comuniques compartiendo memoria; comparte memoria comunicándote."
package main
import "fmt"
func procesarDatos(ch chan string) {
ch <- "Payload procesado exitosamente"
}
func main() {
canal := make(chan string)
go procesarDatos(canal)
// Lectura bloqueante hasta que el dato esté disponible
resultado := <-canal
fmt.Println(resultado)
}
5. Defer para Limpieza de Recursos
La palabra clave defer pospone la ejecución de una función hasta que la función que la contiene retorne. Ideal para cerrar conexiones o archivos sin importar dónde ocurra un error:
file, err := os.Open("config.json")
if err != nil {
return err
}
defer file.Close() // Se ejecutará siempre al salir de la función
Resumen: ¿Por qué usar Go en tu stack?
- Binarios únicos y autocontenidos: Cero dependencias externas en el servidor o imagen Docker.
- Excelente rendimiento con bajo consumo de RAM: Ideal para optimizar costos de infraestructura.
- Curva de aprendizaje limpia: Pocas palabras reservadas y un formateador de código unificado (
go fmt).