Code refactoring techniques — tricks & tips

Do you love code refactoring, but don’t know how to put it into practice? In this article we will discuss three refactoring techniques with which restoring clean code becomes easier: refactoring duplicate code according to DRY, refactoring conditional with polymorphism, and refactoring with the Single Responsibility Principle.

Transparent Data
Blog Transparent Data ENG
8 min readApr 8, 2021

--

Code Refactoring: What, Where and When (intro)

Refactoring has been breaking popularity records among programmers for some time. Due to the fact that it is based on the gradual refreshment of small code fragments without disturbing the logic of their operation, it is perfect for counteracting the technological debt and dealing with legacy systems. Why?

Code refactoring has such undeniable advantages like:

  • it is easy to test and safe to implement (if a system operation logic remains untouched, then the risk of system crash is very small),
  • it can be used even in everyday situations, e.g. when adding a new functionality or conducting Code Review (you do not need to reserve a few weeks ahead for refactoring; often a day or two is enough),
  • as it does not rely on changing the entire infrastructure and does not require mapping all processes like code rewrite, it is very cost-effective.

Below, we discuss three tricks-techniques for good code refactoring.

Refactoring Trick # 1: Refactoring duplicate code according to DRY

If you are a programmer, you have certainly heard about DRY (The Don’t Repeat Yourself Principle), many times. Everyone knows about it, even beginners. Knowing and applying is, however, a gigantic difference. Any code developer knows that they should not duplicate code, but in the end everyone sometimes inattentively succumbs to the opposition of DRY and applies WET (We Enjoy Typing / Write Everything Twice).

Obviously in practice we often deal with code duplications in many places, owing to which refactoring according to DRY is one of the most frequently used tricks to improve the existing code. Why? Because thanks to it, it is extremely easy to track pieces of programming code that need to be refactored. All you need to do is apply The Rule of Three, which makes everything easier.

Rule of Three: up to three times a piece and we’re doing code refactoring!

  1. The first time you do something, you just write the code.
  2. The second time you program something similar, you copy the code (make duplication).
  3. The third time you do something similar again, you can extract it and apply refactoring.

This rule helps you decide when it is worth extracting the duplicate code. What’s more, it’s worth using it even in your own daily work to refresh the code and keep it clean. In short, the point is, when you see code duplication the third time, it’s time to extract it. This is already refactoring.

Code refactoring according to DRY — example

To inspect the point let’s view this code snippet:

Code refactoring according to DRY example

Two objects, Car and Home, have the same cleanWindows method here. It is therefore duplicated and suitable for refactoring.

For instance, you can refactor this code fragment by isolating an abstract class what looks like this:

Code refactoring according to DRY example

However, this solution is not perfect. Although we managed to eliminate code duplication, it should be noted that the dependency hierarchy is not entirely correct here. A car and a house, i.e. our Car and Home objects, are completely different objects in the business sense. If the program is written, for example, for a cleaning company that must be able to assess how many windows it still has to clean in cars and at homes, and on this basis the company assesses the number of employees needed on a given day, then such refactoring will not leave our code cleaner.

In this case, it is definitely better to use the code reuse mechanism, which also allows you to bypass the limits of inheritance, i.e. the trait:

Code refactoring according to DRY example

As you can see, through the code reuse mechanism, Car and Home classes no longer inherit from the same parent, so the object hierarchy is correct at this time.

Refactoring Trick # 2: Replacing Conditional with Polymorphism

As Grady Booch once said:

Clean code is simple and direct.

Hence, complex logical conditionals are always worth running through refactoring. We encounter them extremely often, especially in legacy systems. They are easy to recognize by the fact that the main object, depending on the attribute it has (e.g. type), performs various calculations or actions. Programming code written with many conditionals is extremely difficult to develop and greatly complicates future development. So something needs to be done with it.

A refactoring technique that is hellishly useful in simplifying complex conditionals is polymorphism. It allows you to abstract expressions from specific types, so you can still use variables and values ​​in a variety of ways. Its advantages are that it allows you to efficiently get rid of mass duplicates, and what’s more, it makes adding a new variant of execution limited only to adding a new subclass, which does not require interference with the already existing code.

In understanding what this refactoring technique is helps the knowledge of The Tell-Don’t-Ask Principle. Polymorphism doesn’t ask the object about its status, and then performs an action based on the response. No. It simply tells the subject what to do, with the subject being free to choose how it wants to do it.

How to refactor conditionals with polymorphism?

If we tried to create a mini guide to polymorphism, we would get something like this:

  1. Create subclasses that match the branches of this complex condition (prepare a class hierarchy).
  2. In the created classes, create one shared method, then move the code from the appropriate condition branch to this new method.
  3. Replace the condition with the appropriate method call.

The result? Proper implementation will be achieved through a polymorphism depending on the class of objects.

Replacing conditional with polymorphism — example

Let us assume that we have a vehicle class (Vehicle) which, depend on its type (“bus”, “truck”, “car”), must have different documents for inspection (VehicleDocuments).

Replacing conditional with polymorphism example

One fragment of this class uses a method with complex Switch conditional:

Replacing conditional with polymorphism example

Why is Switch conditional problematic here? Well, it belongs to the so-called code smells. Note that in this particular case it means that we cannot develop the class further without modifying this conditional. So it disrupts the OCP pattern. As a result, it asks for replacing with polymorphism, i.e. inserting certain classes dedicated to specific types instead of Switch:

Replacing conditional with polymorphism example
Replacing conditional with polymorphism example

Now that you have refactored with polymorphism, you can add new subclasses instead of modifying your code every time.

Refactoring Trick # 3: Refactoring with the Single Responsibility Principle (SRP)

If a class has multiple responsibilities, for example two: generating museum tickets and printing them, it is responsible for two processes that may change independently in the future. The content of the tickets may change, as well as their printed format. In that case, it is much better (and more future-proof for clean code) to separate the two responsibilities so that they work independently of each other.

According to Robert C. Martin’s The Single Responsibility Principle (SRP):

There should never be more than one reason for a class or method to exist.

So when you see a snippet with more than one responsibility it’s time to refactor.

Refactoring with single responsibility — example

As an example of code that needs to be refactored, let’s take this one here:

Refactoring with SRP example

As you can see, the Post class has many responsibilities.

When you refactor it according to the Single Responsibility Principle, Post remains in your code, but is only responsible for generating content. In turn, separate JsonPostGenerator and PdfPostGenerator classes are responsible for generating JSON and pdfs:

Refactoring with SRP example

After refactoring each class has a separate responsibility. Additionally, you might even be tempted to create a common interface for PostGeneratorInterface generators, which would require the generate method to be implemented in the classes that implement it.

Refactoring techniques — summary

As you might know, the above three refactoring techniques (DRY, polymorphism, and SRP) do not fully cover the topic. There are many more useful tricks that will help us restore clean code and reduce the technological debt. While the task of your team is not just gradual refactoring of a legacy system code for an external client, where it is necessary to set up a multi-month plan to refresh the most critical points, it is worth using code refactoring on a daily basis, primarily in three situations:

  1. when fixing bugs in the system,
  2. when adding new functionalities,
  3. on the occasion of Code Review.

Thanks to this habit, your code will become pure programming code again — passing all tests, bugs-free, readable for other programmers and cheaper to maintain.

--

--