Process boundaries are borders to hell

Part of https://discourse.jujucharms.com/t/read-before-contributing/47, an opinionated guide by William Reade

Defaults Are A Trap

“OK”, you think, “I will write a config struct, but it’s a terrible
hassle for the client to supply all these values, we know what task we’re really doing, so we’ll just accept nil values and insert clock.WallClock, or "/var/lib/juju", or defaultAttemptStrategy, orwhatever, as appropriate”.

This is wrong for several reasons:

  • you’re smearing the client’s responsibilities into what should be a
    context-agnostic tool
  • you’re making it harder to validate client code, because it
    specifies a bare minimum and just trusts the implementation to do
    what it meant
  • you’re privileging one use case (runtime, in the context you’re
    currently imagining) over another current one (the tests)… and
    over all future uses to which your system might be put

…and it only rarely even aids readability, because the vast majority of types where it’s easy to make this mistake are only constructed once or twice anyway. (When a type has many clients, the folly of defaults is more clearly pronounced, so they tend to slip away. Or, quite often, to remain in place, waiting to trip up someone who underconfigures the type
in some new context and would really have appreciated an immediate “you didn’t say how long to wait” error rather than discovering that you’ve picked a wildly inappropriate default.)

Note that zero values are fine, encouraged even – so long as they are valid as such. The go proverb is “make the zero value useful”, and so you should: but a zero value that’s magically interpreted as a non-zero value is not a “useful zero value”, it’s just in-band signalling and comes with all the usual drawbacks. Know the difference.

(Sometimes you will create Foos over and over again in the same context, such that you only want to specify the important parameters at the point of use. It’s fine to write a local newFoo(params, subset) func: just don’t pollute the Foo implementation with your concerns. Similarly, if you’re really sure that you can supply useful defaults: expose them as
a package func returning a pre-filled config, and make it the client’s explicit choice to defer configuration elsewhere.)