Why You Should Hate ‘else’ in Go: Dave Cheney’s Surprising Coding Rules
The article distills Dave Cheney’s provocative Go coding guidelines—abandoning else, using anonymous structs, simplifying conditionals, and isolating main logic—showing how these counter‑intuitive habits reduce cognitive load, improve testability, and lead to clearer, more maintainable backend code.
1. “If is bad, else is worse”
Cheney recommends initializing a variable with a safe default and overriding it only in production, thereby eliminating the need for an else branch. This prevents the variable from ever being nil after refactoring.
var thing *Thing
if os.Getenv("ENV") == "production" {
thing = NewRealThing()
} else {
thing = NewMockThing()
}A safer version gives the variable a default value first:
thing := NewMockThing() // default safe value
if os.Getenv("ENV") == "production" {
thing = NewRealThing() // override only in production
}Encapsulating the logic in a helper function makes the code reusable and unit‑testable:
func newThing(env string) *Thing {
if env == "production" {
return NewRealThing()
}
return NewMockThing()
}
// usage
thing := newThing(os.Getenv("ENV"))For multiple environment branches, a switch statement is preferred over chained if/else for clarity.
2. Sometimes the best name is no name
When decoding a one‑off JSON payload, use an anonymous struct directly inside the handler instead of defining a named type that lives only for a few lines.
func MyHandler(w http.ResponseWriter, r *http.Request) {
var payload struct {
Name string `json:"name"`
Value int `json:"value"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
// handle error
return
}
// validate payload and build domain object...
}The anonymous struct never escapes the function, keeping the package namespace clean and reducing cognitive overhead.
3. Write conditionals for your brain, not just the compiler
Prefer if status > 399 over if status >= 400. The > operator requires a single mental check (“greater than 399?”) whereas >= forces the reader to consider two cases (greater than 400 or equal to 400), increasing short‑term memory load.
4. main.run pattern: isolate application logic
Fundamentally, if something is hard to discuss, it’s hard to use.
func main() {
if err := run(os.Stdout, os.Args); err != nil {
fmt.Fprintf(os.Stderr, "%s
", err)
os.Exit(1)
}
}
// All application logic lives here.
func run(stdout io.Writer, args []string) error {
// parse flags, set up dependencies, run the app...
// return an error if something goes wrong
return nil
}The run function receives all dependencies explicitly and returns an error, making the core logic a plain, testable Go function without hidden global state.
Conclusion
Avoiding else, using anonymous structs, simplifying conditionals, and moving work out of main into a dedicated run function together reduce cognitive load and produce clearer, more maintainable Go code.
References
GopherCon Europe: https://www.youtube.com/watch?v=RZe8ojn7goo
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.
