Introducción
Go (Golang) es famoso por su simplicidad y concisión. Sin embargo, detrás de la aparente facilidad se esconden trampas que son especialmente engañosas para los desarrolladores que ya han dominado la sintaxis y las construcciones básicas. La transición al nivel "avanzado" no se trata de conocer patrones complejos, sino de comprender profundamente el funcionamiento del runtime, los tipos y la concurrencia.
En este artículo analizaremos los principales errores de los desarrolladores principiantes de Go que van más allá de "olvidé cerrar un archivo" o "confundí nil con un error". Aprenderá cómo evitar fugas de memoria, mal manejo de gorutinas y canales, y comprenderá por qué nil no siempre es nil.
Error 1: Fugas de gorutinas (Goroutine Leaks)
El error más costoso en Go es iniciar una gorutina y olvidarse de ella. Los desarrolladores principiantes a menudo piensan que la gorutina terminará sola tan pronto como la función se ejecute. Pero si la gorutina se bloquea en un canal que nunca recibirá datos, permanecerá colgada para siempre.
Problema: Canal olvidado sin receptor
func process(data []int) chan int { out := make(chan int) go func() { for _, v := range data { out <- v * 2 } close(out) }() return out}
func main() { ch := process([]int{1, 2, 3}) // Leemos solo un valor, el canal no se cerrará fmt.Println(<-ch) // La gorutina se quedará colgada intentando enviar 4 y 6 al canal // time.Sleep(time.Second) — fuga de memoria}Solución: Siempre controle el tiempo de vida de la gorutina. Use context.Context para cancelación o canales con búfer si no está seguro de la cantidad de lectores.
func process(ctx context.Context, data []int) <-chan int { out := make(chan int) go func() { defer close(out) for _, v := range data { select { case out <- v * 2: case <-ctx.Done(): return } } }() return out}Error 2: Uso incorrecto de select con default
La construcción select con default es una herramienta poderosa para operaciones no bloqueantes. Pero los novatos a menudo la usan como un "parche", lo que provoca un busy-loop (bucle infinito con alta carga de CPU).
Problema: Bucle infinito sin bloqueo
for { select { case msg := <-ch: fmt.Println(msg) default: // No hacemos nada, pero el bucle no se bloquea }}Este código consumirá el procesador al 100% de un núcleo. default hace que select no sea bloqueante, y el bucle se convierte en una encuesta infinita.
Solución: O quite default (entonces select esperará), o agregue time.Sleep o time.Tick para reducir la carga.
// Opción correcta: sin defaultfor msg := range ch { fmt.Println(msg)}
// O con tiempo de esperafor { select { case msg := <-ch: fmt.Println(msg) case <-time.After(100 * time.Millisecond): // Verificación periódica }}Error 3: La interfaz nil no es nil
Esta es una trampa clásica de Go. Una variable de tipo interface{} es igual a nil solo si tanto su tipo como su valor son nil. Si asigna un puntero a nil a una interfaz, la interfaz dejará de ser nil.
Problema: Verificación de nil a través de una interfaz
type MyError struct{}
func (e *MyError) Error() string { return "error"}
func getError() error { var e *MyError = nil return e // Devolvemos un puntero nil, pero el tipo no es nil}
func main() { err := getError() if err != nil { fmt.Println("¡Hay un error!")