Not so long ago I read an article about SOLID design principles in Clojure and started thinking it would be interesting to talk about those principles more generally. I don’t know about you, but I have a terrible habit of skimming over stuff like that thinking “oh sure, SOLID, that sounds like a good idea” and then promptly forgetting all about it.
According to Wikipedia, Robert C Martin (aka Uncle Bob) came up with the SOLID design principles in the early 2000s and Michael Feathers came up with the mnemonic acronym to help people remember them. The SOLID principles are meant to help people design code that’s easy to maintain and extend, which keeps future maintenance programmers from wanting to throw chairs at them :)
Uncle Bob has an excellent summary of what SOLID stands for on his site, so I’ll just quote him here:
|The Single Responsibility Principle
|A class should have one, and only one, reason to change.
|The Open Closed Principle
|You should be able to extend a classes behavior, without modifying it.
|The Liskov Substitution Principle
|Derived classes must be substitutable for their base classes.
|The Interface Segregation Principle
|Make fine grained interfaces that are client specific.
|The Dependency Inversion Principle
|Depend on abstractions, not on concretions.
Let’s start with the S, the single responsibility principle. Why is it so important that a class only have one reason to change? Because that means it’s only responsible for one thing. Classes that are responsible for only one thing have conceptual integrity, which is an enormous part of writing code than anyone else can ever use and one of the most important things The Mythical Man Month talks about. Conceptual integrity is kind of a tough concept to nail down, though. I would say that a project that has a high level of conceptual integrity is consistent, it has a predictable design. All of the classes at any given layer of your architecture should behave largely the same way so another programmer doesn’t have to spend hours figuring out how each individual class behaves so she can add her feature.
A surprising number of best practices in software development are about accepting that development is incredibly easy to screw up and attempting to make it harder to make a complete hash of things. If you’re working on a very small project like an assignment in college, it really doesn’t matter whether your project has conceptual integrity. If it’s simple enough that you can hold the whole thing in your head or if it’s something you spend a week building before you hand it in and then never look at it again, you can get away with whatever ridiculous mish-mash of design concepts you want. Where conceptual integrity becomes a huge fucking deal is when you’re maintaining a large system over multiple years.
College/university/bootcamps are great and you’ll learn lots, but one thing that’s very difficult to teach in a limited amount of time is what it’s actually like to maintain an existing system. All of the things you can get away with in tiny throwaway projects just do not work on larger projects that might survive for ten years or more. Every tiny mistake that you make in the beginning gets magnified by years and years of decisions built on top of it.
For example, at a previous job I chose a convoluted hash structure to store the data I needed to build an interface. It seemed like a good idea when I built it, but as the requirements changed – requirements always change, don’t kid yourself on that front – the cracks started to show. I had come up with a brittle design that was extremely difficult to extend. As I tried to force my data structure to support more and more features it just became more and more of a mess. I ended up having to apologize a whole lot to the dev who ended up maintaining that monstrosity after I left and I wouldn’t have been surprised if he had to scrap the whole thing and rebuild it sensibly.
Even if conceptual integrity doesn’t seem like a big deal at this point in your studies or career, trust me, it will come back to bite you soon enough. The more things your class does the longer it takes another dev to figure out how to use it. That other dev could also be you six months from now when you’ve forgotten all the fiddly little details of how you built that class. The less predictable your codebase is, the more time you waste re-learning things every time you use a given class.
It probably doesn’t sound that bad to have to relearn one class before you use it. It only takes a few minutes, right? Where things get ugly is where you have to relearn “just one class” five times to get a feature done or even worse, when you didn’t realize you needed to relearn that class because the whole rest of that layer worked the same way so you reasonably assumed that class did too. You get some really interesting bugs when just one of these things isn’t like the others and what’s worse, they’re especially difficult to catch with unit tests because hardly anyone thinks to test for the possibility that this one class doesn’t behave like all of the other classes like it.
The more areas a class has responsibility for, the less predictable it is. If I have a service that retrieves data from a store (maybe it’s a relational db, maybe it’s a nosql db or a cache), I can pretty safely make assumptions about what the get and save methods do. But if I have a service that retrieves data and formats it for display, I can’t know it formats things the same way as all the other controller classes or if it does any special business logic until I go and look.
The more areas a class has responsibility for, the more other classes have to change if it changes. A class that just represents database data only changes if we add new fields to the database. If we add a new field, it’s completely logical and predictable that other classes that use that class might need to change to handle the new field. If a class handles retrieval and some business logic, then it’s much harder to find all of the classes that need to change if that class changes and it’s much more likely that we might miss something that needs to change too. It sounds stupid if you’re used to teeny tiny projects, but this is stuff that actually happens when you have a codebase of over a thousand classes. The more your project does, the more careful you have to be about keeping everything tidy. Think about that poor maintenance programmer and don’t make her wish she could hunt you down and pelt you with tomatoes.
As much as we like to think programming is a solitary activity, a huge amount of professional programming is actually about not being a complete asshole to the devs who will come after you :)
The open/closed principle is coming soon!