SOLID Is Not Just For Objects

Part of Read before contributing, an opinionated guide by William Reade

SOLID Is Not Just For Objects

Dave Cheney did a good talk on this sometime earlyish in 2016. To hit the high points (and add my own spin, I think I disagree a bit with some of his interpretations…):

Single Responsibility Principle

Do one thing, do it well. If some exported unit is described as doing “X and Y”, or “X or Y”, it’s failing SRP unless its sole purpose is the concatenation of, or choice between, X and Y.

Open/Closed Principle

Types should be open for extension but closed for modification. Golang is designed to make it easy to do the right thing here, so just follow community practice, and embrace the limitations of embedding – it really is helping you to write better code.

In particular, if you’re planning to do anything that reminds you of OO-style inheritance, you’re almost certainly failing here.

Liskov Substitution Principle

Roughly speaking, anywhere you can use a type, you should be able to use a subtype of same without breaking; and I’m pretty sure golang basically gives us this for free by eschewing inheritance. You still have to pay some attention to the semantics of any interface you’re claiming some type implements, but that’s an irreducible problem (given the context) and seems to have relatively minor impact in practice. I wave my ands and move on.

Interface Segregation Princple

Group your interfaces’ capabilities so that they cover one concern, and cover it well. Your underlying types might not – when working with any sort of legacy code, they surely will not – but you don’t have to care: it’s far better to supply the same bloated type via 3 separate interface params than it is to accept a single bloated interface just because you need to consume a badly-written type.

Sometimes you want to combine these tiny interfaces, when the capabilities are sufficiently bound to one another already – see package io, for example – but you should feel nervous and mildly guilty as you do so, and the more methods you end up with the worse you should feel.

Dependency Inversion Principle

Depend on abstractions, not concretions: that is to say, basically, only accept capabilities via interfaces. Certain parts of the codebase have been written so as to make it as difficult as possible to rewrite them to conform to this principle; please chip away at the problem as you can, all the same.

Also, think about what you really depend on. If you just use some resource, accept that Resource directly, and don’t muck about with ResourceFactory or NewResourceFunc unless you really need to defer that logic. SRP, remember – unless resource creation is fundamental to what you’re doing, it’s best to let someone else handle it.

1 Like

Cheney elaborates in his blog: