Part of https://discourse.jujucharms.com/t/read-before-contributing/47, an opinionated guide by William Reade
Multiple parameters are pretty nice at times, but the weight of evidence leans in favour of making most of your funcs accept either 0 args or 1. The sensible limit is probably, uh, 3 maybe? …but even then, it’s actually depressingly rare to nail a 3-param signature such that you never need to update it; and callables tend to accumulate parameters, anyway.
So, when you’re exporting any callable that you expect to churn a bit in its lifetime (i.e., pretty much always) just take the few extra seconds to represent the params as a type. There’s a howto wiki page somewhere.
If you defer it until the first instance of actual churn, that’s fine, it’s a judgment call; but if you find yourself rewriting an exported signature even once, just take the time to replace it with a struct. (And then future changes will be much easier.)
Also, take a couple of minutes to see if you’re rediscovering a
widely-used type; and make an effort to give it a name that’s a proper noun: FrobnosticateParams
or -Args
is generally a pretty terrible choice, it’s super-context-specific and betrays very little intent. For example:
type CreateMachineArgs struct {
Cloud string
Placement string
Constraints constraints.Value
}
func CreateMachine(args CreateMachineArgs) (Machine, error)
…is, ehh, comprehensible enough, I suppose. But that’s an awful name! You can immediately make it a bit better by just calling the type what it really is:
type MachineSpec struct {
Cloud string
Placement string
Constraints constraints.Value
}
func CreateMachine(spec MachineSpec) (Machine, error)
…and then, as a bonus, you get a type that doesn’t hurt your eyes when it ends up being a generally useful concept and passed around elsewhere.
I have found the following nouns generally better than either Args or Params, in various contexts:
-
Spec
(implies creating distant resources, e.g. in the db, perhaps?) -
Config
(implies sufficient dependencies/info to perform a task?) -
Context
(implies you’re a callback?) -
Request
(implies, well, a direct request) -
Selector
(implies request for several things, possibly to set up a
Request?)
…but please don’t treat this as a prescription: find the right names and use them. e.g. a LeaseClaim
, or whatever; and often, indeed, just a string is quite good enough.
func (repo *Repo) Get(name string) (Value, bool)
…is just fine as it is, because it probably really won’t ever change significantly.
One caveat: do not casually modify types that are used as config structs, and pay particular heed to the names-are-for-clients advice above. You may well have several different types that vary in only one or two fields: that certainly shouldn’t result in consolidation to one type alone, but it may help you to discover a single type that is widely used on its own, and occasionally alongside one or two other parameters.
Resist the temptation to consolidate so far that a type’s validity depends upon its context.