Code review in Go: what to check and how to give feedback
\nCode review is not just a formal check before merging a pull request. It is a powerful tool for improving code quality, sharing knowledge within the team, and preventing errors at early stages. For beginner Go developers, participating in a review can seem stressful: it's scary to receive comments or, conversely, to miss a critical error. In this article, we will break down what to pay attention to when reviewing Go code, and how to give feedback so that it is useful, not offensive.
\n\nWhy is code review especially important in Go?
\nThe Go language was designed with a focus on simplicity, readability, and reliability. However, the simplicity of the syntax does not eliminate the complexity of the logic. Typical problems in Go projects: ignoring errors, incorrect work with goroutines, resource leaks. A good code review helps identify these and other issues before they reach production.
\n\nWhat to check in Go code: a checklist for beginners
\nTo make the review systematic, use a checklist. Here are the main points to check in every pull request.
\n\n1. Error handling
\nIn Go, errors are values; they cannot be ignored. Check that every function returning error handles it or explicitly passes it up the call stack.
// Bad: ignoring the error\nfile, _ := os.Open("config.yaml")\n\n// Good: handling the error\nfile, err := os.Open("config.yaml")\nif err != nil {\n log.Fatalf("cannot open config: %v", err)\n}\ndefer file.Close()\nAlso check that errors are wrapped using fmt.Errorf("...: %w", err) to pass context, rather than just being logged and swallowed.
2. Naming and readability
\nIn Go, short names are used for variables (e.g., r for reader, w for writer) and long names for exported functions. Check that:
- \n
- Exported functions, types, and variables have comments (starting with the entity's name). \n
- Variable names reflect their purpose (not
a,b,tmp). \n - There are no unnecessary abbreviations (
calcis better replaced withcalculate). \n
3. Working with goroutines and concurrency
\nGoroutines are a powerful but dangerous tool. Check:
\n- \n
- Is there a synchronization mechanism (channels, mutexes) for shared data. \n
- Are there any "leaking" goroutines (goroutine leak). Use context (
context.Context) for cancellation. \n - Is a goroutine blocked forever (deadlock). \n
// Example of safe work with a goroutine\nfunc worker(ctx context.Context, jobs <-chan int, results chan<- int) {\n for {\n select {\n case job, ok := <-jobs:\n if !ok {\n return\n }\n results <- job * 2\n case <-ctx.Done():\n return\n }\n }\n}\n\n4. Resource management (defer)
\nCheck that all opened files, network connections, and database transactions are closed using defer. This should be done immediately after successfully opening the resource.
// Correct: close immediately\nconn, err := net.Dial("tcp", "example.com:80")\nif err != nil {\n return err\n}\ndefer conn.Close()\n\n5. Testing
\nBeginners often forget about tests. Ensure that unit tests are written for new logic. Check:
\n- \n
- Tests cover not only the happy path but also edge cases. \n
- Is
t.Runused for subtests. \n - Are there any tests that depend on external resources (DB, network) without mocks. \n
How to give feedback: 5 rules for beginners
\nThe technical part is half the battle. The other half is the ability to communicate. Here a