Introduction: Why Code Review in Go Is Not Just a Checkbox
\nCode review is not a bureaucratic procedure, but a key element of development culture. In Go, with its concise syntax and strict rules, review often becomes the first filter that catches problems early on. Many teams transitioning to Golang find that old habits (e.g., from Python or PHP) don't work: Go forgives fewer type-related errors but requires attention to detail.
\nIn practice, code review in Go solves three tasks: team training (newcomers adapt to the code style faster), quality improvement (finding bugs and anti-patterns), and code consistency (so the project doesn't become a "patchwork quilt"). Let's break down what exactly to look for and how to formulate comments.
\n\n1. What to Check First: From Syntax to Errors
\nAny review starts with the basics. In Go, there are several "hot spots" worth checking automatically or manually.
\n\n1.1. Error Handling
\nGo does not use exceptions — errors are returned as values. A common problem: a developer ignores the returned error using _. This is only acceptable in rare cases (e.g., fmt.Println).
// Bad: error is ignored\nresp, _ := http.Get("https://api.example.com/data")\n\n// Good: error is handled\nresp, err := http.Get("https://api.example.com/data")\nif err != nil {\n log.Printf("failed to get data: %v", err)\n return err\n}\ndefer resp.Body.Close()\nWhat to check:
\n- \n
- Are all errors handled (not just
_)? \n - Is
deferused to close resources (files, connections)? \n - Are errors in goroutines being "swallowed"? \n
1.2. Naming and Style (Go Idioms)
\nGo requires brevity: camelCase for private, PascalCase for exported. Avoid "Hungarian notation" and long names.
// Bad\nfunc GetUserDataFromDatabase(userID int) (*UserData, error)\n\n// Good\nfunc GetUser(id int) (*User, error)\nRed flags:
\n- \n
- Variable names like
temp,data,val(too generic). \n - Functions longer than 30-40 lines (need to be broken up). \n
- Missing comments for exported functions (this is a
golintrequirement). \n
1.3. Goroutines and Synchronization
\nThis is the most complex part. Errors in concurrent code are a frequent source of bugs in production.
\n// Bad: data race\nvar counter int\nfor i := 0; i < 100; i++ {\n go func() {\n counter++ // unsafe\n }()\n}\n\n// Good: use a mutex\nvar mu sync.Mutex\nvar counter int\nfor i := 0; i < 100; i++ {\n go func() {\n mu.Lock()\n counter++\n mu.Unlock()\n }()\n}\nWhat to check:
\n- \n
- Is there a
sync.Mutexoratomicfor shared data? \n - Was the channel closed (
close(ch))? \n - Are goroutines leaking (no
selectwith a timeout)? \n
2. Architectural Checks: From Interfaces to Packages
\nAfter basic syntax, look at the code structure. Go encourages composition, not inheritance.
\n\n2.1. Interfaces: Small and on the Consumer Side
\nIn Go, interfaces are implemented implicitly. A good interface contains 1-3 methods. Check if the author is trying to create a "comprehensive" interface.
\n// Bad: huge interface\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\