Understand the Pitfalls of Complicated Solutions
There's an urban legend about the American and Soviet space programs. Think about this: How do you write in a zero-gravity environment? The legend says that the Americans and Soviets had different answers:
American engineers invented a self-pressurized pen.
The Soviets opted for a pencil.
Over-engineering is a trap you could fall into too! 😅
And it’s not just at the start when over-engineering could pose a problem. Software systems grow in complexity all the time. Users want new features, and you must deliver. But adding those features can lead to bad designs if you are not careful.
Here's how you run into problems. The first feature is easy to write. You figure out what needs to be done, design it, and put the code in place.
Now comes another feature, which is a lot like the first one - but a bit different. How do you put in this new feature? Well, there's always copy/paste. Just duplicate the existing code and modify it a little to take care of the new feature!
But over time, this can result in lots of repeated code, which becomes difficult to maintain. The next developer to add or modify a feature has to change your code in many places, and they might overlook something. 😱
Or you could add the new feature by adding more responsibility to a class or function that already exists. Since it’s already doing most of the work, what's a bit more?
That doesn’t sound like a problem to me! Why is it so strongly recommended against?
First, the solution gradually gains complexity. Fewer people may understand how it works. So you’re wasting future developers’ time as they have to figure it all out.
Second, the architecture becomes brittle. When every piece of code depends on too many other pieces, you eventually get to the point where you have to redesign half your application to fix one little thing.
Imagine if one of the pedals broke on your bicycle. It should be simple to replace it - any bike mechanic could fix it in minutes! But if the bike were designed badly, with too many interdependencies, you might have no other choice but to throw away both pedals and the whole gear mechanism. 😞
How do you avoid writing solutions that go too far? Or ones that become difficult to understand and modify?
Keep it simple!
One advantage is, it is easier to understand. If it's easier to understand, then it's easier to modify. Plus, you can be more confident that the modification won't break anything.
Another is that it is easier to test. When your classes are well-separated, they are easier to test.
This is easier said than done. Fortunately, you can leverage the knowledge of those who came before and found better ways of doing things! You’ve already seen this with the design patterns in the previous part of the course. Now we’re going to take this further with the SOLID design principles.
Identify the SOLID Principles
Each letter in SOLID represents an excellent idea to keep in mind when architecting your system. We will examine each one in-depth as the course progresses. We'll also put them into practice by improving our card game application. Let's get an overview of these key ideas:
"S" is for single responsibility.
Each class or function should do one thing and do it well. It should only have one reason to change.
"O" is for open/closed principle.
A class should be open to extension but closed to modification.
What on Earth does that mean?
Well, ideally, it should be easy to add a new concept to the system by extending the original functionality without duplicating loads of code. And ideally, you shouldn’t have to make modifications to existing code in the process.
"L" for Liskov substitution.
Subclasses should be able to do everything that their parent classes can. If you replace a parent class with one of its subclasses, it shouldn’t break your system!
"I" for interface segregation.
Essentially, the single responsibility principle, applied to interfaces.
"D" is for dependency inversion.
Parent classes shouldn't have to change when one of their subclasses is modified.
As you will see, these design patterns are effective because they demonstrate these principles! We will be using them to improve the card game we wrote when we explored MVC.
Cleaner designs are easier to understand, maintain, modify, and test.
Following the SOLID principles leads to cleaner design.
The SOLID principles are:
Meet me in the next chapter, and we'll start looking at these in detail - beginning with the single responsibility principle!