Code review en Go: qué revisar para que el código no explote
\n\nEl code review no es solo una casilla que marcar en Jira o una excusa para pelearse con un compañero en los comentarios del pull request. Es, quizás, la forma más honesta de no dispararse en el pie en producción. Y cuando hablamos de Go —un lenguaje que se adora por su simplicidad y se odia por «¿dónde están los genéricos?» (spoiler: ya existen)—, la revisión se convierte en un arte. En este artículo, analizaremos exactamente qué observar durante un code review en Go, para que tu código no solo funcione, sino que también sea agradable a la vista. ¡Allá vamos!
\n\n1. Manejo de errores: no los ignores, no les temas
\n\nEn Go no hay excepciones. Aquí todo es honesto: si una función puede fallar, devuelve un error. El problema más común de los principiantes es ignorar los errores usando el guion bajo. Recuerda: «Never ignore an error» no es solo un mantra, es una ley.
Qué revisar en el código:
\n- \n
- Verificación de err != nil — debe estar en todas partes donde una función devuelva un error. Incluso si estás seguro de que «aquí no habrá error». \n
- Registro o propagación — el error debe registrarse con contexto o devolverse hacia arriba. Silenciarlo es de mala educación. \n
- Comparación con errores centinela — usa
errors.Is()yerrors.As(), no la comparación directa con==. \n
// Mal: ignoramos el error\nfile, _ := os.Open("config.yaml")\n\n// Bien: verificamos y registramos\nfile, err := os.Open("config.yaml")\nif err != nil {\n log.Printf("no se pudo abrir la configuración: %v", err)\n return err\n}\ndefer file.Close()\n\n\n2. Gorutinas y concurrencia: orden en el caos
\n\nLas gorutinas son el superpoder de Go. Pero un gran poder conlleva una gran responsabilidad. En las revisiones, a menudo vemos fugas de gorutinas, condiciones de carrera y bloqueos. Un code review en Go sin verificar la concurrencia es como conducir un auto deportivo sin cinturón de seguridad.
\n\nLista de verificación para gorutinas:
\n- \n
- Inicio de gorutinas — siempre controla su ciclo de vida. Usa
sync.WaitGroupo contextos. \n - Condiciones de carrera — verifica que el acceso a datos compartidos esté protegido con un mutex o un canal. Ejecuta
go test -race. \n - Pánico en una gorutina — si una gorutina entra en pánico, todo el programa falla. En código de producción, usa siempre
recover. \n
// Mal: gorutina sin control\ngo func() {\n // puede fallar y matar todo el proceso\n doWork()\n}()\n\n// Bien: con WaitGroup y recover\nvar wg sync.WaitGroup\nwg.Add(1)\ngo func() {\n defer wg.Done()\n defer func() {\n if r := recover(); r != nil {\n log.Printf("la gorutina falló: %v", r)\n }\n }()\n doWork()\n}()\nwg.Wait()\n\n\n3. Interfaces: pequeñas y al grano
\n\nEn Go, las interfaces son contratos. Pero, a diferencia de Java, no tienen que ser enormes. En la comunidad de Go existe el principio: «Cuanto más pequeña sea la interfaz, mejor». Un método es ideal. Dos está bien. Tres ya es sospechoso.
\n\nQué observar:
\n- \n
- Tamaño de la interfaz — si tiene más de 3-4 métodos, considera dividirla. \n
- Acepta interfaces, devuelve estructuras — la sabiduría clásica de Go. Hace que el código sea flexible y testeable. \n
- No definas interfaces por adelantado — que las cree el consumidor, no el productor. \n
// Mal: interfaz enorme\ntype Storage interface {\n Save(data []byte) error\n Load(id string) ([]byte, error)\n Delete(id string) error\n List() ([]string, error)\n Connect() error\n Close() error\n}\n\n// Bien: interfaces pequeñas\ntype Saver interface {\n Save(data []byte) error\n}\ntype Loader interface {\n Load(id string) ([]byte, error)\n}\n