10 Go Code Review Rules for Safer, Cleaner Backend Development
This article presents ten practical Go code‑review guidelines—covering error handling, premature abstraction, concurrency, scope, safety, early returns, file organization, declaration order, semantic naming, and purposeful comments—to help teams write more reliable and maintainable backend code.
1) Handle Errors Properly, Don’t “Eat” Them
Never use _ = err or ignore errors.
When handling errors, either log them or return them to the caller, avoiding double reporting.
Optimize return values for callers: return result, nil (acceptable) return nil, err (acceptable) return nil, nil (ambiguous, avoid) return result, err (unclear, avoid)
res, err := svc.Do(ctx)
if err != nil {
// Choose one: log or return
return nil, fmt.Errorf("do: %w", err)
}
return res, nil2) Avoid Premature Abstraction (Don’t Introduce Interfaces Too Early)
Start with concrete types; introduce interfaces only when multiple implementations are truly needed.
Follow the convention: accept interfaces as parameters, return concrete types.
Don’t create interfaces solely for testing; prefer real implementations in tests.
// ✅ Start with concrete type
type FileStore struct { dir string }
func (s *FileStore) Save(b []byte) error { /*...*/ return nil }
// ✅ Define interface only when needed
type Saver interface { Save([]byte) error }
func Upload(s Saver, b []byte) error { return s.Save(b) }3) Prefer Mutex Over Channel for Shared State
Begin with synchronous code; only add concurrency when a bottleneck is proven.
Use sync.Mutex or sync.WaitGroup for shared state; resort to channels for complex orchestration to avoid double‑close or deadlock issues.
type Counter struct {
mu sync.Mutex
n int
}
func (c *Counter) Inc() { c.mu.Lock(); c.n++; c.mu.Unlock() }4) Declare Variables Near Their Use (Minimize Scope)
Declare constants, variables, and types in the closest file or scope where they are needed.
Keep function‑local variables close to their usage to reduce shadowing and refactoring cost.
if ok := check(id); !ok {
return errors.New("bad id")
}
// "ok" declared right where it is used5) Prevent Runtime Panics
Validate external inputs (requests, storage, deserialization) before processing.
Check for nil before dereferencing; better yet, use value types instead of pointers when possible.
Trust Go’s error handling in controlled paths; avoid scattered if x == nil checks.
type Profile struct {
Name string // value type is safer
}6) Reduce Indentation with Early Returns
Move error checks and negative conditions to the top of the function, keeping the main execution path flat.
if err := validate(req); err != nil {
return err
}
// Main flow is now flatter7) Reject “Catch‑All” Utility Files
Avoid generic files like util.go, misc.go, or constants.go that become dumping grounds.
Group code by domain meaning and place it near the code it influences.
8) Order Declarations by Importance
Put public APIs or entry functions first; implementation details and helpers follow.
This lets readers see usable functionality before digging into internals.
9) Name by Semantics, Not by Type
Avoid suffixes like userMap, idStr, injectFn.
Match variable names to their scope: larger scopes require clearer, more specific names.
10) Write Comments That Explain “Why”, Not “What”
Document the motivation and trade‑offs behind decisions rather than restating the code.
Future readers usually understand what the code does but often miss why it was written that way.
// To avoid allocations on hot paths we reuse buffers from a pool, sacrificing readability for throughput.One‑Page Checklist for Teams
Error handling: single‑path reporting; clear return semantics; avoid nil, nil.
Abstraction: start concrete, keep parameters as interfaces and returns concrete; no “interface for testing”.
Concurrency: begin synchronous; use Mutex/WG before channels.
Scope: declare near use; keep small scopes small.
Safety: validate external input; prefer value types over pointers.
Structure: early returns to flatten code.
Organization: reject catch‑all files; name by domain and place code nearby.
Ordering: public API first, details later.
Naming: semantic names, more specific for larger scopes.
Comments: explain why and trade‑offs.
References & Further Reading
GoLab 2025 agenda and speaker page.
Talk: “Writing Better Go: Lessons from 10 Code Reviews”.
Speaker Deck slides with latest points and examples.
Discussion on Golang Weekly #575 and r/golang.
This article is a Chinese localization and engineering‑focused version of the talk, intended as a ready‑to‑use code‑review checklist for teams.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
