Policies for coding
William Reade
william.reade at canonical.com
Mon Aug 26 15:12:54 UTC 2013
On Fri, Aug 23, 2013 at 5:47 PM, Nate Finch <nate.finch at canonical.com>wrote:
> It does require a little discipline when creating methods to avoid the
> mental default of just writing a method to take the concrete type that you
> expect to receive, and instead look at what methods you use from that type
> and see if it's more appropriate to use an interface that type implements.
>
The trouble comes when one of the methods on the interface returns
something non-trivial. Consider the following:
-------------------------------------
package example
type Fooer struct{
// non-trivial
}
func (*Fooer) Foo() *Barer {
return &Barer{
// private fields
}
}
type Barer struct{
// non-trivial
}
func (*Barer) Bar() {}
-------------------------------------
...and a client func somewhere else:
-------------------------------------
package client
import "example"
func FooBar(f *example.Fooer) {
f.Foo().Bar()
}
-------------------------------------
Of course, this was written without taking the above advice into
consideration, so of course it's not mockable. Let's try:
-------------------------------------
package client
import "example"
type Fooer interface {
Foo() *example.Barer
}
func FooBar(f Fooer) {
f.Foo().Bar()
}
-------------------------------------
Great! We accept an interface, and follow good practice, and everyone's
happy, until we try to mock it.
-------------------------------------
package client_test
import "client"
import "example"
type MockFooer struct {}
func (MockFooer) Foo() *example.Barer {
// ...wait a moment
}
-------------------------------------
If example.Barer has private fields, for example, or otherwise cannot be
constructed without the aid of a concrete example.Fooer, we're no better
off than we were before. We have to go to the example package and change
*its* interface in a breaking way (leaving aside naming problems, which are
not insignificant, the return type of Fooer.Foo() has to change).
It's enough of a hassle updating the client code that you control when you
have to make a change like this; and if anyone *else* depends on that
package, game over. Frequently you just sigh, and use the concrete types,
and move on. And the client test suite just has to suck it up and become
that little bit less wieldy.
And that's why we should generally prefer to return interfaces -- because
the cost of doing so and not needing to is negligible, but the cost of
doing after the fact approaches the prohibitive with alarming stealth and
speed. This is not to say that you *can't* return structs... but you almost
certainly shouldn't return structs with private fields, or structs whose
public fields could take a lot of work to set up, or structs that might
legitimately come to fall under those categories.
I don't know the exact problems you were having, Tim, but maybe a more
> specific example would help us (or at least me) understand the problem
> you're trying to solve.
>
Tim may have found other problems, but that's the general shape of the ones
I've found. Interfaces are great, but you have to *use* them, and it's not
trivial to bolt them on.
> On Fri, Aug 23, 2013 at 5:37 AM, roger peppe <rogpeppe at gmail.com> wrote:
>
Perhaps we could do it this other way around?
>>
>> // TODO (thumper) Details about the change needed
>> // 2013-08-23 bug 1654321
>>
>
Plenty of todo descriptions are multiline; I'd rather follow tim's model
please.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.ubuntu.com/archives/juju-dev/attachments/20130826/fccfee91/attachment.html>
More information about the Juju-dev
mailing list