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

  1. Caller names configuration fields
  2. Constructor validates combinations
  3. 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

01

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.

02

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.

go
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
Read the code

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.

03

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.

04

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.

05

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.

06

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.