Rule of thumb
If behaviour is stateless — no config that varies per instance, no dependencies to inject — package-level variables and functions are cleaner than a struct + constructor.
| Use constructor | Use package-level |
|---|---|
| Config varies per instance | Config is fixed / constant |
| Fields change over lifetime | Behaviour is stateless |
| Injecting dependencies (e.g. DB for tests) | No dependencies to inject |
| Implementing an interface for swappability | A plain function is the whole API |
Example: location name sanitiser (Beep)
Regex patterns for stripping prefixes/suffixes like [Upcoming] never change at runtime.
// ❌ Before — unnecessary constructor
sanitiser := location_name_sanitiser.NewLocationNameSanitiser()
result := sanitiser.Sanitise(name)
// ✅ After — compiled once on import, zero boilerplate
var prefixPatterns = compilePrefixPatterns()
var suffixPatterns = compileSuffixPatterns()
func Sanitise(name string) SanitisationResult { ... }
result := location_name_sanitiser.Sanitise(name)var at package scope initializes once on first import — as safe and efficient as sync.Once, with none of the ceremony.