Image for post
Image for post

BETTER SOFTWARE DESIGN

Refactoring From Trash to SOLID

Amp up your code quality, the easy way.

Nicklas Millard
Aug 9, 2020 · 6 min read

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.

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.

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.

Class doing logging and database operations
Class doing logging and database operations
Class doing logging and database operations

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.

Split one class into two for better separation
Split one class into two for better separation
Split one class into two for better separation

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.

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.

Poorly written format method
Poorly written format method
Poorly written format method

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.

Refactored to strategy classes
Refactored to strategy classes
Refactored to strategy classes

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 RegisterFormatter().

The 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.

Example of using a formatter class with registered formatters
Example of using a formatter class with registered formatters
Example of using a formatter class with registered formatters

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.

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.

No interface segregation
No interface segregation
No interface segregation

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 UpdateUserCommand and UserManager has almost stayed the same, with only a few changes.

That’s how easy it is to clean up messy code.

Extracted interfaces from the UserManager
Extracted interfaces from the UserManager
Extracted interfaces from the UserManager

Take a second to think of the implications in regards to testing, and creating custom implementations of IUserUpdater. It all became so. much. easier.

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.

Class violating DIP and explicit dependencies principle
Class violating DIP and explicit dependencies principle
Class violating DIP and explicit dependencies principle

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 GetUsersCommand class.

Stop newing up classes and take interfaces as constructor arguments. It’s honestly that simple.

The refactored class that adheres to DIP
The refactored class that adheres to DIP
The refactored class that adheres to DIP

Again, this incredibly simple approach has just relieved us from pain-inducing testing.

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.

Medium's largest active publication, followed by +756K people. Follow to join our community.

Nicklas Millard

Written by

Tech writer with 701K+ views. Sharing my opinion and what I learn. Danish C# backend engineer in FinTech. Ex Big4 senior tech consultant.

The Startup

Medium's largest active publication, followed by +756K people. Follow to join our community.

Nicklas Millard

Written by

Tech writer with 701K+ views. Sharing my opinion and what I learn. Danish C# backend engineer in FinTech. Ex Big4 senior tech consultant.

The Startup

Medium's largest active publication, followed by +756K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store