Code review em Go: o que verificar para o código não explodir
\n\nCode review não é apenas uma marcação no Jira ou um motivo para brigar com um colega nos comentários do pull request. É, talvez, a maneira mais honesta de não dar um tiro no pé em produção. E quando se trata de Go — uma linguagem amada pela simplicidade e odiada por "onde estão os genéricos?" (spoiler: eles já existem) —, a revisão se torna uma arte. Neste artigo, vamos analisar exatamente o que observar durante um code review em Go, para que seu código não apenas funcione, mas também agrade aos olhos. Vamos lá!
\n\n1. Tratamento de erros: não ignore, não tenha medo
\n\nEm Go não existem exceções. Aqui tudo é honesto: se uma função pode falhar, ela retorna error. O problema mais comum de iniciantes é ignorar erros usando sublinhado. Lembre-se: "Never ignore an error" não é apenas um mantra, é uma lei.
O que verificar no código:
\n- \n
- Verificação de err != nil — deve estar em todos os lugares onde a função retorna um erro. Mesmo se você tiver certeza de que "aqui não vai dar erro". \n
- Registro ou propagação — o erro deve ser registrado com contexto ou retornado para cima. Abafá-lo é falta de educação. \n
- Comparação com erros sentinela — use
errors.Is()eerrors.As(), em vez de comparação direta com==. \n
// Ruim: ignorando o erro\nfile, _ := os.Open("config.yaml")\n\n// Bom: verificando e registrando\nfile, err := os.Open("config.yaml")\nif err != nil {\n log.Printf("não foi possível abrir o config: %v", err)\n return err\n}\ndefer file.Close()\n\n\n2. Goroutines e concorrência: ordem no caos
\n\nGoroutines são o superpoder do Go. Mas com grandes poderes vêm grandes responsabilidades. Na revisão, frequentemente vemos vazamentos de goroutines, condições de corrida e bloqueios. Code review em Go sem verificar concorrência é como dirigir um carro esportivo sem cinto de segurança.
\n\nChecklist para goroutines:
\n- \n
- Inicialização de goroutines — sempre controle o tempo de vida. Use
sync.WaitGroupou contextos. \n - Condições de corrida — verifique se o acesso a dados compartilhados está protegido por mutex ou canal. Execute
go test -race. \n - Panic em goroutine — se uma goroutine entrar em pânico, todo o programa cai. Em código de produção, use obrigatoriamente
recover. \n
// Ruim: goroutine sem controle\ngo func() {\n // pode falhar e matar todo o processo\n doWork()\n}()\n\n// Bom: com WaitGroup e 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("goroutine falhou: %v", r)\n }\n }()\n doWork()\n}()\nwg.Wait()\n\n\n3. Interfaces: pequenas e objetivas
\n\nEm Go, interfaces são contratos. Mas, ao contrário do Java, elas não precisam ser enormes. Na comunidade Go, existe o princípio: "Quanto menor a interface, melhor". Um método é ideal. Dois é normal. Três já é suspeito.
\n\nO que observar:
\n- \n
- Tamanho da interface — se tiver mais de 3-4 métodos, considere dividi-la. \n
- Aceite interfaces, retorne structs — sabedoria clássica do Go. Isso torna o código flexível e testável. \n
- Não defina interfaces antecipadamente — deixe que o consumidor as crie, não o produtor. \n
// Ruim: interface 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// Bom: interfaces pequenas\ntype Saver interface {\n Save(data []byte) error\n}\ntype Loader interface {\n Load(id string) ([]byte, error)\n}