Over time, code seems to get harder and harder to support and understand. This is because good SOLID software architecture principles aren't typically followed. In this series of short videos, Geoff Godwin covers five solid principles you can use to improve your coding practices.
Single Responsibility Principle
Simply put, the Single Responsibility Principle, or SRP, is the idea that each class, function or module in your code should have only one responsibility. This way you can later update or replace it very easily, while avoiding spaghetti code implementations that plague so many software projects.
Think of it like this: if you were building a car, you wouldn't want one part to handle both steering and braking, right? That would be a recipe for disaster!
Instead, you'd want each part to have a single, well-defined purpose, so that the whole machine still works together smoothly, but each individual part can also be worked on or replaced separately without unforeseen side effects.
The same goes for software code. By keeping each module focused on one job, you can create a more reliable, flexible, and maintainable system. So, the next time you're tempted to add more functionality to an existing class or module, remember the single responsibility principle: stick to doing one thing.
Think of your smartphone for a sec: You can add a lot of functionality to it by buying add-on accessories such as a case, cam lenses, a kickstand, pop socket, wallet, or even backup battery. None of those functions required you to open the phone up and change it though. You simply extended the purpose of the device with other compatible devices.
When it comes to designing really good software, this is referred to as the open-closed principle of software architecture. In a nutshell, it says that software should be open to extension but closed to modification.
In software development, this is the equivalent of creating interfaces or abstract classes so that your code has intended uses and expectations, but the concrete implementation is left up to the developer extending it.
If you use composition, this should be second nature as you are composing objects together to give them a purpose, in inheritance this is trickier but best left up to subclasses to extend the functional purpose of the super class!
So, remember when you’re designing your next piece of code to be sure it’s open to extension but closed to modification. Think of the eventual uses of your code in later iterations and design your abstractions accordingly!
Liskov Substitution Principle
If my refrigerator dies, I should be able to go buy a new one and replace my current one as-is and trust it can do the job of my old one. If, instead, I replaced my fridge with an oven, I’d have a rough time of it.
In software architecture, this principle is known as the Liskov Substitution Principle. Sounds fancier than it is, but it means that when you have subclasses inheriting from a base class, that they should be able to do everything the base class can do plus more.
I could extend my Fridge class with the “IceFridge” class and have it still chill my food but ALSO make ice! Or I could extend it with a “WaterFridge” that keeps the food cold, but also serves water out the front of the door.
This seems like a simple enough thing to keep in mind but really helps you figure out where specific functionality should go in your program. It promotes both code reusability in the form of inherited traits and good code contracts, as well as extensibility to aid with keeping to the Open-Closed Principle.
It also helps with the Dependency Inversion Principle which I cover below.
By sticking to the Liskov Substitution Principle, software becomes more robust, maintainable, and scalable. It fosters good design practices and enables easier integration of later features you haven’t even dreamt up yet.
Interface Segregation Principle
How annoying is it that you can’t JUST get the TV shows and movies you want without having to pay for all the stuff you don’t want?
The same thing happens in bad software architecture, but it doesn’t have to. This is where the Interface Segregation Principle shines.
Let me lay it out for you: Developers using your code should never have to implement interfaces they aren’t going to use. This means that instead of creating one megalithic interface with all your necessities, you’re better off splitting your interface into multiple interfaces and allowing the consumers of your code to implement only the ones they need.
So, you wouldn’t create a class called Messaging that has methods for texting, voice, and images, because your consumers may only need one or two of those functions. Instead, you’d follow the interface segregation principle and split those text, voice, and image functions into their own interfaces so that they can be consumed a la carte.
This way, your consuming engineers aren’t burdened with needless dependencies on interfaces they don’t use, promoting more flexible and maintainable software systems.
Dependency Inversion Principle
Imagine a restaurant that depends on a few specific food suppliers to get their ingredients. By working directly with these companies and their processes, the restaurant is tightly coupled to the success or failure of these individual suppliers. This is clearly bad for the restaurant if things go sideways for any of these companies.
So, instead let’s consider the Dependency Inversion Principle, a software architectural concept that promotes looser coupling and modularity between related classes. If instead of dealing with individual suppliers our restaurant deals with an open marketplace of vendors, they can decouple their tight relationship with the supplier and instead rely on a broader set of resources.
The marketplace acts as an abstraction between the restaurant and multiple suppliers and means not only that adding and removing individual suppliers will no longer directly require changes to the restaurant, but also that healthy market competition is possible between suppliers to offer the restaurant the best implementation of ingredients.
By following the Dependency Inversion Principle we’ve provided a far more modular and extensible solution to the problem.