Legacy Coderetreat: Part 16 – Refactor Conditionals by Decomposing Conditional

Refactor conditionals by Decompose Conditional

Blog post series

This blog post is part of a series about legacy coderetreat and legacy code techniques you can apply during your work. Please click to see more sessions about legacy code.

IF-THEN-ELSE-END_flowchart

Purpose

We often see code with conditionals that are hard to understand. The purpose of this technique is to make the code look readable by extracting a function with the condition. It will be easier to test the code if the condition is extracted and injected as a dependency.

This technique is useful when having good variable names is not enough to explain the condition. We can have some of the following reasons for wanting to extract a function with the condition:

  • The condition is duplicated in several parts of the class
  • The condition is duplicated along different classes
  • Test the condition in isolation, because of its complex nature
  • Test the code without understanding how the conditions work (inject conditions as stubs)

 

Concept

I. Identify all the conditions to be extracted

In the last episode we refactored some code with unclear conditionals by extracting variables that explain the condition(s)

to a state where the code is easier to understand.

But we can do even more, we can apply the Decompose Conditional refactoring, we can extract a method with all the condition.

Step 1: Identify conditionals that can be extracted to a method

We are looking for all the conditions in the method and we chose the first one.

Because we explained the variable in the previous episode, we can now go on with extracting the function.

Step 2: Extract the method locally

We just extracted the function and gave it the same name as the existing variable.

Step 3: Give a good name to the extracted method

We already performed the Variable refactoring and therefore have a good name for the variable. We can reuse the name for the function we extracted. So we do not need to do anything during this step.

Step 4: Verify the code works well

As always, it is important to run your unit tests, component tests, and maybe even integration tests if needed. Make sure to test the functionality manually as well.

II. Iterate and extract all conditions into functions

You get the idea, after we extract all the functions from the conditions we end up having code that looks like this:

 

On a normal code base this would be the last part. But when working on existing code with a bad structure and design we often have some conditions that stop us from testing the code. For example let’s take this code:

 

 

If I had a condition that is hard to meet, I would want to be able to test the body of the conditional easier.

For that we can extract all the conditions to another class.

III. Extract conditions to another class

We can extract all the functions to a new class called RollCondition

 

 

that implements the interface IRollCondition

 

 

We would inject the IRollCondition to the Game class through the constructor. The purpose of creating this interface is to be able to inject a stub of the conditionals to the System Under Test and in this way we can test easier the production code.

 

 

And the roll() method would look like this

 

What we can do now, is have a clearer view on how the code works. We can also test the code, without caring about the conditions themselves.

 

In a future session we will go into details on how to use conditional injection to existing code so that it can become easier to test. We will go into details with the pros, cons and traps of this way of refactoring tangled existing code.

 

IV. Verify the code works well

As always we need to verify if the code works well.

Outcomes

We started with a code that was already refactored in the previous episode with the Explaining Variable technique. The code looked pretty good because it was already refactored.

We extracted the functions from the conditions, and the code looked even better, cleaner than before. This is the part where traditionally decomposing conditionals as a refactoring stops.

We went beyond that and extracted the conditions to a class, and injected the conditions so that we can test easier the code.

At the end, we have two views: the conditions (class and interface) and the “working” code (roll function). We succeeded to separate the two, and we can focus on each one separately. We can look at the “working” code and try to improve it, considering that the conditions themselves are well implemented. We are able to have this separation also because the names of the functions that are called in the condition describe very well what needs to happen beyond the conditional’s gate.

Remarks

It is very important to understand what the code does in this case. The name of the functions needs to be leading the reader to the right direction. I want to stress the importance of good names when extracted on existing code, as we never want to mislead the future reader of the code.

Often the code is not in a good shape, and if we want to be able to extract functions that make sense from the conditionals, we often need to refactor a bit before decomposing conditions. Make sure you have all the elements and knowledge needed when starting this refactoring.

As you can observe, we extracted pure functions from the conditions. This allows an easier collaborator management, as all the needed collaborators are sent as a parameter to the function.

Careful: extracting the conditions can be dangerous if we permit anyone to inject any implementation of a the interface. This could lead to strange defects or even disastrous crashes of the system. Whenever something like this is needed, try to perform strict reviews on the code and some contract tests would be extremely useful.

Careful: it is very important to write test that make sense, when stubbing the condition functions. This technique is very useful, but the data we use in the tests need to be proven as correct in a production system. Otherwise the only thing we do is to write useless tests, because the condition would never permit the system to behave in such a way.

History

As far as I know the technique of explaining variable was first documented in the book Refactoring, by Martin Fowler.

In the past 4-5 years I have extended the use of this technique by extracting the functions to a separate class and extracting the interface from it with the purpose to inject the conditions. Working this way have saved me a lot of time when I have tested existing, tangled code.

Code Cast

Will be published soon…

Image Credits

http://upload.wikimedia.org/wikipedia/commons/d/d3/IF-THEN-ELSE-END_flowchart.png

Subscribe

If you want to receive an email when I write a new article, subscribe here:

Subscribe for new articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Post Navigation