BETTER SOFTWARE DESIGN
Refactoring From Trash to SOLID
I’m not going to preach theory. Since you decided to click this cryptic title, I assume you’re already familiar with the SOLID principles abbreviations.
Chances are you’ve already looked at a bunch of other articles, tutorials, and videos about SOLID. No shame in having to get more perspectives on the same topic.
I’ll show you how easy it is to refactor garbage code, and hopefully, you’ll have a foundation to improve your code quality.
Luckily, it’s incredibly easy to write SOLID code.
Let’s get one thing straight before diving into code
So, I have no chance of demonstrating every aspect of each principle. I’ll merely be demonstrating the principles using a few practical examples. You have to do your own thinking from thereon. Incorporate what you think works. Discard what doesn’t.
Make your classes do one thing well
If you have to modify a class when a new feature comes along, you might be looking at a single responsibility violation. Typically, this happens whenever you need to implement cross-cutting concerns, such as logging, caching, etc.
So, this example snippet clearly shows a class having too many responsibilities. It’s both handling repository related operations as well as logging.
We refactor this by creating two separate classes. One is used to decorate the other. Note I’ve also extracted an interface and the decorator class takes an instance of the interface as a dependency. That’s how easy you can decorate a class.
Extremely simple and effective.
Do you see how the
UserRepository got a whole lot simpler, and now only has one responsibility? Logging is no longer its concern.
We’ve created a separate
UserRepositoryLoggingDecorator class that takes care of logging.
Tip: you can also refactor to commands and queries. It’s a near perfect pattern to use when making classes simpler.
Don’t Modify Existing Classes
This is my absolute favorite principle. Being able to just add new classes and not touch existing ones is d*mn awesome.
You quickly spot OC violations. Characterized by massive
if-else or switch branching, or, every new feature requires a new
if. Just like this product formatter, taking a product type and returns a string formatted in some desired way.
Upfront it doesn’t look too bad. It’s quite simple.
However, for every new formatting type, you have to violate OCP by modifying the method. Also, if there’s a bug and you need to fix formatting logic, you need to do so at a place surrounded by unrelated code.
Using polymorphism to remove insane, unnecessary if-else is one of the best, and easiest, ways. In this way, you also touch upon the Liskov’s Substitution Principle.
Now, we’ve refactored the above, inflexible code. Take whatever time you need to read thru this code. You and I will walk thru it after.
So, you have obviously noticed how much more code there’s now. Refactoring is not about minimizing code, it’s about making code easier to deal with and reason about.
First off, we take each logic chunk inside the if and else-if and place it inside its own formatter class. Each class now has a single purpose. When you need to fix a bug, you know all the surrounding code is related to it.
Secondly, each formatter type is registered with the
ProductFormatter by calling its
Format() implementation has been changed to use lookup which formatter to use, as opposed to handling the formatting itself.
Also, notice there now is a JSON formatter. Simply by adding a class for it. It was that simple.
Here’s an example of how to use the product formatter.
Beginner programmers and computer science majors will no doubt raise their eye-brows and immediately say this is overly complicated or not good for performance. Well, there’s a trade-off with everything. That’s life.
Tip: look into using code reflection to create simple, highly extensible code.
Only use what’s relevant to your class
You’ve probably used or even made your own swiss army knife classes. This is for instance service and manager classes. Generic enough to container whatever. They’re basically logic dumpsters.
Say we have a command class used to update a user. This class takes a dependency on a
UserManager containing the nitty-gritty user stuff.
It’s all mighty fine. But, we can do better.
As of now, the UpdateUser class can freely access any method exposed by the user manager. Not exactly what we want, right?
We’ll replace the swiss army knife dependency with a single-purpose tool like in the snippet below.
Notice how the
UserManager has almost stayed the same, with only a few changes.
That’s how easy it is to clean up messy code.
Take a second to think of the implications in regards to testing, and creating custom implementations of
IUserUpdater. It all became so. much. easier.
Decouple your classes
You’ve already been exposed to the dependency inversion principle thru out this article. It’s all about relying on abstractions over concrete classes. It’s that simple.
So, the class below is violating multiple principles. It’s ‘newing’ up its dependency on
AppDatabase. A big no-no.
Instead of ‘newing’ up concrete instances, you’d take them as explicit dependencies, and, you’d want to rely on abstraction instead of a concrete class. Simply put, you’d want to take an interface as a constructor argument.
Take a look at this refactored
Stop newing up classes and take interfaces as constructor arguments. It’s honestly that simple.
Again, this incredibly simple approach has just relieved us from pain-inducing testing.
“The number of classes explodes with every refactor your make!”
This might be one objection you have to my refactoring approach.
Remember refactoring is not about minimizing code or even necessarily about making things “simpler”. It’s about changing your code to match your application’s complexity and making your code easier to work with.
Simple != easy.
Resources for the curious
-------------------------What to look for in Code Review - JetBrains BlogpostDependency Inversion Principle by OODesign
Nicklas Millard is a software development engineer in one of the fastest-growing banks, building mission-critical financial services infrastructure.
Previously, he was a Big4 Senior Tech Consultant developing software for commercial clients and government institutions.
Connect on LinkedIn