Ten Rules to Refactor Code

In one of my previous articles, I explained Code Refactoring, what it is, why do we need it, its advantages and disadvantages.

DISCLOSURE: This article contains affiliate links to external products. This means that I will earn a commission at no additional cost to you if you decide to make a purchase. Thank you!

I want to dive deeper into code refactoring in this blog post. I'm going to refer to the book " Five Lines of Code ", written by Christian Clausen.

I particularly enjoyed the book because the author takes a different approach to refactor code than the one usually explained in terms of "code smells".

What are the rules to refactor code?

The author maps out some rules which have to be put in the proper context if you want to maximize their benefits.

Let's see what these rules are.

#1 RULE: A method should not have more than five lines

This is also the title of the book.

An extended method can be classified as a "code smell".

This rule prevents a method from doing too much.

If a method does too many tasks, it's best to break it down into smaller methods.

#2 RULE: Either call or pass

A method should do two things:

  1. Call methods on an object.

  2. Pass the object as an argument.

Not both.

P.S: the examples in the book use Typescript.

  • sum(arr) is the high-level abstraction.

  • arr.length is the low-level abstraction.

  • In example 3.15, the calculation follows a high-level abstraction only.

#3 RULE: Never use switch statements

Switch statements tend to be used a lot, especially by beginners.

The problem with them is that:

❌We don't always have to do something for each value we set (indeed, they support default).

❌It's easy to forget a break statement in-between values.

The author suggests avoiding putting functionalities into default clauses.

#4 RULE: Only inherit from interfaces

Usually, we would inherit from classes or abstract classes.

The author disrupts this by saying that we should only inherit from interfaces because we promote tight coupling when sharing code between classes.

In addition, duplicated code is challenging to maintain.

#5 RULE: No interfaces with only one method

We shouldn't have interfaces that only have one implementation.

This helps us reduce boilerplate code.

#6 RULE: Avoid using getters and setters

I found this rule probably the most controversial.

Often, we are taught that getters and setters are how we achieve encapsulation.

We make the properties private, but the getters and setters public.

There is a problem with this, though.

❌With getters, anyone who gets the object can call its public methods, which means that we can possibly change its behaviour in a way that we don't expect.

❌With setters, we risk introducing an additional layer of indirection where we can change the internal data structure.

✅To overcome this, the author suggests rethinking the application's architecture to be "push-based".

A push-based architecture is a type of architecture where we pass data as arguments.

This way, all classes end up having functionalities.

#7 RULE: Never have common affixes

Often, we engineers enjoy using affixes to name our variables.

An example of this can be:

String endTime;
String endDate;

The problem with doing this is that it can lead to coherence.

The author suggests using classes instead because they give us more control over the external interface.

#8 RULE: If only at the start

If statements should always be at the top of a method.

Nothing else should happen after the If statement.

This doesn't mean that we should separate the If from the else (they go together).

If we need to perform anything else after an If statement, we should extract the rest into a separate method.

#9 RULE: Never use If with else

We should not use If with else unless we check against a data type.

Using if-else statements makes the code more rigid because we're saying that a decision has to be made at a specific point.

The author refers to if-else statements as early binding, a type of code smell.

When we compile our program, a
behavior—like if-else decisions—is resolved and locked into our application and
cannot be modified without recompiling

The contrary of early binding is late binding.

The opposite of this is late binding, where
the behavior is determined at the last possible moment when the code is run

The author invites us to use objects instead, which allow for more flexibility.

#10 RULE: Use pure conditions

A pure condition is a condition that doesn't provoke any side effects.

A condition that provokes side effects means assigning values to variables, throwing exceptions, etc.

We should avoid this to separate getting data from changing data.

These are the rules that the author explains in the book.

A question that came to my mind when reading the book was:

What makes an application easy to break?

An application becomes fragile when changing something in one place causes a break in another place.

The underlying cause of this fragility in the book is known as the "global state".

"Global" means that something is outside of the scope.

"State" refers to anything that can change when running a program.

Whenever we make properties global, they become exposed to misuse.

The book defines invariants all the properties we don't want to check in the code explicitly.

The author suggests localizing invariants. This means that things that change together should stay together.

==========================================

Overall, I found the book informative and insightful. The book contains more content such as:

  • Refactoring patterns.
  • Game-based application of the refactoring rules explained above.

And much more. As mentioned previously, code snippets are in Typescript.

You can get the book here.

Use the code blmaddy21 to get 35% off all Manning Books products in all formats.

I hope you've found this article helpful.

Until next time! 👋🏾

Other articles of mine that you might enjoy reading:

24