Tu código Go: de "funciona" a "aprobado"
\n\nHas escrito código en Go. Compila. Las pruebas están en verde. Estás orgulloso. Pero llega la code review de un desarrollador senior, y en cinco minutos tu pull request está cubierto de comentarios rojos. ¿Te suena?
\n\nGo es un lenguaje donde la "simplicidad" es engañosa. Detrás de la aparente facilidad se esconden decenas de trampas que un ojo experimentado ve de inmediato. En este artículo analizaremos las observaciones más frecuentes que los seniors dejan en las revisiones de código Go. No solo sabrás qué está mal, sino también cómo corregirlo para que tu código pase la revisión a la primera.
\n\nVamos a analizar los errores típicos en una golang code review.
\n\n1. Manejo de errores: no solo if err != nil
\n\nProblema: ignorar errores o "return" vacío
\n\nEl problema más común de los novatos es un manejo de errores poco informativo. Simplemente devolver el error hacia arriba es solo el 50% del trabajo. Un senior siempre preguntará: "¿Qué salió mal? ¿Dónde?".
\n\n// Mal: el senior dirá "agrega contexto"\nfunc ReadConfig(path string) (*Config, error) {\n data, err := os.ReadFile(path)\n if err != nil {\n return nil, err // ❌ Sin contexto\n }\n // ...\n}\n\n\nEl enfoque correcto es envolver los errores usando fmt.Errorf y el marcador de posición %w (para su posterior verificación con errors.Is o errors.As).
// Bien: el senior asentirá\nfunc ReadConfig(path string) (*Config, error) {\n data, err := os.ReadFile(path)\n if err != nil {\n return nil, fmt.Errorf("read config file %s: %w", path, err) // ✅ Contexto + cadena\n }\n // ...\n}\n\n\nProblema: if err != nil repetitivo
\n\nGo es conocido por su verbosidad en el manejo de errores. Pero eso no significa que debas conformarte con copiar y pegar. Un senior sugerirá extraer las comprobaciones repetitivas en helpers o usar el patrón "errgroup" para las gorutinas.
\n\n- \n
- No hagas esto: Tener 10 veces seguidas
if err != nil { return ... }en una misma función es normal, pero si el patrón se repite en diferentes funciones, es hora de refactorizar. \n - Consejo del senior: Usa
deferpara cerrar recursos y verificar errores de cierre (sí,file.Close()también devuelve un error). \n
2. Concurrencia: condiciones de carrera y canales incorrectos
\n\nProblema: acceso a variable compartida sin sincronización
\n\nGo es un lenguaje donde las gorutinas son baratas, pero las condiciones de carrera son caras. Un senior notará de inmediato si escribes en un map o slice desde varias gorutinas sin un mutex.
\n\n// Mal: data race\nvar counter int\nfor i := 0; i < 100; i++ {\n go func() {\n counter++ // ❌ ¡Carrera!\n }()\n}\n\n\nSolución: usar sync.Mutex o atómicos. Pero el senior dirá: "¿Y no sería más fácil usar canales?".
// Bien: canales para sincronización\nch := make(chan int, 100)\nfor i := 0; i < 100; i++ {\n go func() {\n ch <- 1\n }()\n}\nvar counter int\nfor i := 0; i < 100; i++ {\n counter += <-ch // ✅ Sin carreras\n}\n\n\nProblema: fuga de gorutinas
\n\n¿Olvidaste cerrar el canal? La gorutina se queda colgada para siempre. Un senior en la revisión de código Go seguramente verificará si tienes una gorutina sin un select con ctx.Done().
// Mal: la gorutina puede quedarse colgada para siempre\ngo func() {\n for msg := range ch {\n process(msg)\n }\n}()\n// Si ch nunca se cierra — fuga de memoria\n\n\nEl patrón correcto es pasar siempre context.Context y escuchar la señal de cancelación.
// Bien: protección contra fugas\ngo func(ctx context.Context) {\n for {\n select {\n case msg, ok := <-ch:\n if !ok {\n return // canal cerrado\n }\n process(m