Skip to content

Instantly share code, notes, and snippets.

@peterbourgon
Last active September 25, 2015 15:34
Show Gist options
  • Save peterbourgon/c30510fd7068f5a7981f to your computer and use it in GitHub Desktop.
Save peterbourgon/c30510fd7068f5a7981f to your computer and use it in GitHub Desktop.

in re: weaveworks/scope#515 (comment)

Whats the downsides of this approach? I'm not a huge fan of the factory style, if only because it pollutes potentially clean APIs (it would work better if golang had optional parameters...)

The downside is that it pollutes the global namespace—all else equal, our goal should be to have no global state—and makes testing harder, i.e. this

oldStubThing := stubThing
defer func() { stubThing = oldStubThing }()
stubThing = func() Thing { return mockThing{123} }

c := newComponentActuallyUnderTest(...)

is worse than this

f := func() Thing { return mockThing{123} }
c := newComponentActuallyUnderTest(f, ...)

not only because it's more verbose, but because the dependency is spooky action at a distance (poke global state here, poke other thing there, assume they know about each other) rather than declarative (constructor depends on its parameters).

It's especially bad when the thing being passed in is sufficiently specialized to "make sense" in the context of the owning component. So, time.Now or time.After probably doesn't make sense to treat this way, as those are generally totally orthogonal to the WhateverManager that uses them. But in this instance, a conntracker factory is pretty closely coupled.

Maybe a rule that approximates this behavior could be: if it's in the stdlib, it's fine to mock, but if it's our code, it should be passed as a dependency?

And I will pretend I didn't hear you advocate in favor of optional parameters :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment