Refactoring and S.O.L.I.D Principles

Refactoring and S.O.L.I.D Principles

Another day another Lint-School session.

We learned about refactoring and what to do before you start refactoring things we talked about SOLID principles as well when we were refactoring and saw the OCP and SRP in action

So I took some notes about refactoring and what to do before and when refactoring and when writing code so you write better and cleaner code

so, let’s get started!

You got a task on a new project, a legacy project, where you're required to add a new feature.

you open the code, the networking code is in the view controller, he caches the response in the user defaults as well as writing the caching code in the view controller, there are a lot of random magic numbers thrown around doing god knows what calculations...

What is the first thing to do?


Understand what’s going on first, then start writing tests, If you can’t just writing prints on the functions. verifying those prints is enough

After you have done that, start refactoring.

When Refactoring, identify problems first by…

  1. Start reading the class functions, see if the function has more than 1 responsibility then start breaking them down

  2. Start analyzing class responsibilities from the broken functions, see if something is off or shouldn’t be here, then start taking it out to another component and make sure you pick an appropriate name to it, after all, software design is about two things, naming things and putting them in their correct place

  3. Start writing documentation to the classes you are refactoring stating their responsibilities so you don’t forget

  4. Start analyzing dependencies (variables) and see how the class is coupled with each other

  5. start changing these dependencies into protocols to make the class depend on protocols rather than concert Implementations which is what the DIP states

  6. If a function contains magic numbers or calculations start taking those numbers to scope-local variables with descriptive names and use them, these add great readability with too little changes

  7. Always remember SOLID Principles, and SOLID is about Discipline, about being consistent

  8. When writing components, adding functions, ask yourself, is this component fulfilling more than 1 responsibility?

  9. Two reasons to change, two responsibilities, start refactoring

Now we talk a bit about the SOLID principles...

Dive deep into SOLID Principles

Violations of SRP

Let’s say you have a cache layer that all it does is caching, in-memory cache, realm or core data, and we want to cache user token, then a new requirement is added and now we have to renew the token if it expires.

We have to add validations if the token is expired we don’t cache it, we start adding it to the cache layer… now, is that the right decision? from the above list we check does this adds to the responsibilities of the cache layer?

Well no it is not the right decision, because it now manages the cache and also validates if the token is valid for caching, now we start to separate the validation in a validator class, and move this into the business layer.

Always ask yourself when adding components, does this add to the responsibilities of the same class? of the function? And from there, you will know if you violated the SRP or not

OCP (Open - Closed Principle)

So what is the open-closed principle? In plain English, it means that when you have a feature and you want to add code to it, you don’t go into it and start modifying it, like when adding a new feature you don’t start modifying an existing feature, instead you start extending this feature, by maybe add new functionality by adding another class.

For example, if I have a validation suite,

Bad Example: Build one big class that do validations for the whole project, and when I need to add new validations, I add to that class

Good Example: Break down my validations into a couple of classes each doing one thing, so that when I need to add new functionality, I create a new class to validate the phone number format for example

Perks of OCP is you have less work to do, and less risk of breaking things, it’s additive.

LSP (Liskov Substitution Principle)

The LSP states that the objects should be substituted with their subtype without affecting the correctness of the system.

Example:- If you have a client that depends on a cache protocol, (the cache protocol is the type it depends on), it shouldn’t matter if the client interacts with a subtype of the protocol, an in-memory cache, realm or core data cache If it does matter, you're violating the Liskov principle and thus you created more coupling between components, and thus, you have a rigid components

ISP (Interface Segregation Principle)

The ISP is about dividing your protocols into small protocols to ensure that clients don’t submit to unwanted requirements by the protocol Doing so enables you more freedom in cofronting to what you want, and leave what is unneccessary In Swift, you can extend that protocol and do a default implementation to those functions or variables which is basically the the same thing.

DIP (Dependency Inversion Principle)

Dependency Inversion States that High-level components should not depend on low-level components or modules, they should depend on Abstractions Protocols rather than concrete implementations

Example:- Instead of letting your high-level components like UseCases, Interactors depend directly on frameworks like Realm, for example, they should depend on some kind of abstraction, then the Realm implementation conforms to that abstraction.

Because if you do, you can develop, test, maintain, extend your high-level modules, your policies, your business rules in isolations and in parallel, you can have someone working on the high-level rules and someone working on the low-level details, like Realm implementation

But make sure you don’t leak your low-level details in the abstraction, like passing parameters in your abstraction like NSManagedObject (a Core Data class), then you're leaking the implementation details to the abstraction, then you cannot test it in isolation, you can not develop it in isolation, u can not replace the framework with another framework easily, during tests or because you have a new framework that you want to use.

If you find that you're importing a module in the file of the abstraction like a protocol, and those import modules from the details side, then that’s probably not good, because think about it, you want to be decoupled from the implementation details, you want the framework depending on the business rule, not the opposite.

Violations of DIP

Making high-level components depending on the low-level ones, like importing core data on your high-level business rules


Why do we need SOLID Principles?

Good software, build projects that are a pleasure to work with, collaborate better, build flexible, modular systems, that are easier to maintain, test, develop and extend, replace even These things lead to exceptional salaries, more fulfillment, happiness

What happens if you don’t apply SOLID Principles?

The code is going to rot, coupling, code smells everywhere, no clean abstractions, everything gets in each other way, thousand of code lines in the same file, debug hell. Not following the SOLID principles is a perfect recipe for Spaghetti code

Why is it so hard to apply the SOLID Principles?

Because it depends on your case, there is no one silver bullet that’s going to solve all your dependency issues, your modularity issues Most of these things are counter-intuitive