54. Designing APIs: the Go proverbs, applied
54.1 "Accept interfaces, return structs"
A function that accepts a narrow interface (io.Reader, not
*os.File) works with anything satisfying that shape; a function that
returns a concrete struct gives callers full access to every field and
method, instead of forcing them through whatever subset an interface
happened to expose. Returning an interface is occasionally right (when
you deliberately want to hide the concrete type, e.g. a constructor for
an internal implementation detail) but should be the exception, not the
default.
54.2 "The bigger the interface, the weaker the abstraction"
A one-or-two-method interface (io.Reader, Storage.Get/Set) can be
satisfied by almost anything, which is exactly what makes it reusable. A
ten-method interface can usually only be satisfied by one real
implementation and a hand-written mock — at which point it isn't really
abstracting anything, just adding indirection.
54.3 "Don't just check errors, handle them gracefully"
// Weak: propagates without adding anything a caller couldn't already see
if err != nil {
return err
}
// Better: adds context that helps whoever reads this error three layers up
if err != nil {
return fmt.Errorf("loading config from %s: %w", path, err)
}
54.4 Avoid premature abstraction
A single interface with one implementation and no second implementation in sight is usually just ceremony — Go's implicit interface satisfaction (Chapter 9.1) means you can introduce the interface later, exactly when a second implementation (or a test double) actually shows up, with zero cost paid up front. This mirrors the general engineering principle: three similar concrete structs are better than one premature interface built to anticipate a variation that may never arrive.
54.5 Real-world example
The Storage interface from Chapter 9 earns its keep specifically
because it does get a second implementation (CachedStorage wrapping
it) — that's the signal an interface was the right call, retroactively.
If it had stayed at one implementation forever, a plain
*MemStorage struct with no interface would have been equally correct
and slightly simpler.