Files
wagslane 9be3074de6 first
2023-05-01 15:25:27 -06:00
..
2023-05-01 15:25:27 -06:00
2023-05-01 15:25:27 -06:00

Clean Interfaces

Writing clean interfaces is hard. Frankly, anytime youre dealing with abstractions in code, the simple can become complex very quickly if youre not careful. Lets go over some rules of thumb for keeping interfaces clean.

1. Keep Interfaces Small

If there is only one piece of advice that you take away from this article, make it this: keep interfaces small! Interfaces are meant to define the minimal behavior necessary to accurately represent an idea or concept.

Here is an example from the standard HTTP package of a larger interface thats a good example of defining minimal behavior:

type File interface {
    io.Closer
    io.Reader
    io.Seeker
    Readdir(count int) ([]os.FileInfo, error)
    Stat() (os.FileInfo, error)
}

Any type that satisfies the interfaces behaviors can be considered by the HTTP package as a File. This is convenient because the HTTP package doesnt need to know if its dealing with a file on disk, a network buffer, or a simple []byte.

2. Interfaces Should Have No Knowledge of Satisfying Types

An interface should define what is necessary for other types to classify as a member of that interface. They shouldnt be aware of any types that happen to satisfy the interface at design time.

For example, lets assume we are building an interface to describe the components necessary to define a car.

type car interface {
	Color() string
	Speed() int
	IsFiretruck() bool
}

Color() and Speed() make perfect sense, they are methods confined to the scope of a car. IsFiretruck() is an anti-pattern. We are forcing all cars to declare whether or not they are firetrucks. For this pattern to make any amount of sense, we would need a whole list of possible subtypes. IsPickup(), IsSedan(), IsTank()… where does it end??

Instead, the developer should have relied on the native functionality of type assertion to derive the underlying type when given an instance of the car interface. Or, if a sub-interface is needed, it can be defined as:

type firetruck interface {
	car
	HoseLength() int
}

Which inherits the required methods from car and adds one additional required method to make the car a firetruck.

3. Interfaces Are Not Classes

  • Interfaces are not classes, they are slimmer.
  • Interfaces dont have constructors or deconstructors that require that data is created or destroyed.
  • Interfaces arent hierarchical by nature, though there is syntactic sugar to create interfaces that happen to be supersets of other interfaces.
  • Interfaces define function signatures, but not underlying behavior. Making an interface often wont DRY up your code in regards to struct methods. For example, if five types satisfy the fmt.Stringer interface, they all need their own version of the String() function.

Optional: Further reading

Best Practices for Interfaces in Go