Clean code in Go · 3 MIN READ
Configuration: validate once at construction
Turn invalid setup into an explicit startup error.
Turn invalid setup into an explicit startup error. A constructor is a useful boundary for validating configuration that should remain true for an object’s lifetime. Trace the worked Go example, then compare the benefit with the cost before applying this design to your own project.
Clean Go · A MAP OF THE IDEA
- Caller names configuration fields
- Constructor validates combinations
- Resources start only after validation
Useful when
- Invalid settings should fail at startup
- An object needs stable invariants
Watch out
- Option interactions still need validation
- Partial resource acquisition needs cleanup
Implementation & operation cost: Low
The design decision
A constructor is a useful boundary for validating configuration that should remain true for an object’s lifetime. Use a named config struct when options are already clear. Defaults should be deliberate, and a zero value should not silently disable a safety limit unless documented.
A complete Go example
Read this program from the entry point and follow the calls shown in the diagram. The output is included at the end so you can check the behavior without a compiler.
package main
import (
"errors"
"fmt"
)
type Config struct {
Workers int
QueueLimit int
}
type Pool struct{ config Config }
func NewPool(c Config) (*Pool, error) {
if c.Workers <= 0 || c.QueueLimit < c.Workers {
return nil, errors.New("invalid pool limits")
}
return &Pool{config: c}, nil
}
func main() {
p, err := NewPool(Config{Workers: 2, QueueLimit: 8})
fmt.Println(p.config.Workers, err)
_, err = NewPool(Config{Workers: 0})
fmt.Println(err)
}
// Output:
// 2 <nil>
// invalid pool limits
NewPool rejects zero workers and a queue smaller than the worker count. The successful caller receives a valid configuration. No goroutines are started in this example, which keeps validation separate from lifecycle and cleanup.
Tradeoffs and the simpler alternative
Use functional options only when they improve an evolving API. A constructor with many independent options can hide invalid combinations; validate the final configuration before opening files or starting goroutines, and clean up partial acquisition on later failure.
Build a typed configuration at startup
Read environment variables and files at the executable boundary, parse durations and sizes into typed values, then validate required combinations before starting workers. Pass a completed configuration value to constructors. Re-reading environment variables deep in business logic makes tests and runtime behavior unpredictable. Apply defaults explicitly, and distinguish an omitted value from a meaningful zero. Do not log raw configuration if it contains secrets.
Define which changes can happen at runtime
Immutable configuration is simpler: restart a process to change it. If reload is required, parse and validate a whole new snapshot before atomically replacing the active one. Existing requests should see one consistent snapshot, not a mix of old and new fields. Some changes, such as a database destination, need resource lifecycle handling rather than swapping a string. Preserve the previous valid configuration when reload fails.
Test invalid combinations before deployment
Cover an empty required address, malformed duration, negative retry count and a timeout smaller than a required operation budget. Verify secrets never appear in error text or diagnostic dumps. Constructors should fail with actionable context rather than leaving partially initialized workers alive. Functional options can help an API with many independent options, but they do not remove the need to validate incompatible choices after all options are applied.
TAKE THIS WITH YOU
Rejects invalid combinations before acquiring resources. Use functional options only when they improve an evolving API. A constructor with many independent options can hide invalid combinations; validate the final configuration before opening files or starting goroutines, and clean up partial acquisition on later failure.
Check your understanding
Which statement best describes Configuration: validate once at construction?
Go to the source
Progress stays in this browser. No account needed.