After a recent discussion, I’ve realised something about Go’s interfaces. They’re best if you use them as a consumer rather than producer. Using my own code as an example. I have a storage layer that uses bigtable.
package storage type IO interface { Read() (string, error) Write(string) error } type bigtable struct {…} fun New() Bigtable { return &bigtable{…} } func (bt *bigtable)Read() (string, error) {…} func (bt *bigtable)Write() error {…}
So, we’re creating an interface then returning an instance of it. This allows us to create a fake version simply.
package fakestorage type Fake struct { value string err error } func (f *Fake)Read() (string, error) { return f.value, f.err } func (f *Fake)Write() error { return f.err }
The consumer just takes the interface.
package logger type Service struct {…} func New(io storage.IO) Service { return &Service{io} }
So what’s the problem here? Well, we’re constraining the output of storage.New()
with an eye towards the Service
consumer. But there’s no need. The Bigtable struct would adequately satisfy the interface. By returning the interface we can only call the specified methods. There’s no possibility to call something that (e.g.) we only want in startup.
As a bonus, if we return the concrete type, we get far better results in godoc.