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-proyecto

  • Descargar paquetes y limpiar dependencias no usadas:

    bash
    go mod tidy

  • Ejecutar en desarrollo directamente:

    bash
    go run main.go

  • Compilar 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.

go
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:

go
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

go
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."

go
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:

go
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).