Parallel Change Refactoring

Oleksii Fedorov

Parallel Change is the refactoring technique that allows implementing backward-incompatible changes to an API in a safe manner. It consists of 3 steps:

  • Expand — add new functionality as an extension of the interface (e.g.: add new methods), instead of editing signatures of existing interfaces
  • Migrate — mark (or log a warning) existing interface as deprecated and give time to the clients of the interface to migrate to the new interface. That might be as simple as changing your code-base one client of this interface at a time in case when you do not have 3rd party clients of these interfaces
  • Contract — once all clients have migrated remove old interfaces

This technique is tremendously useful when you have 3rd party clients for your API (open-source library, SaaS with REST API, etc.). Also, it is as useful for typical application development because it allows such breaking changes never to break your test suite. It allows deploying your code in the middle of the refactoring, in fact, every few minutes (Continuous Integration + Continuous Deployment).

This article contains code examples in a pseudo-code.

UserSearch

Let’s imagine that we have some class, that is used to search User by id:

Now, new requirement comes in, and it seems, that we need to be able to search by e-mail too, so we have to add more functionality here:

Later, new requirement comes in, and now we need to be able to search by the nickname too. So we follow the pattern:

That is great and all, but we clearly can see, how this class violates Open-Closed Principle: every time there is a new thing to search the user by, we will have to alter this class. That is not okay. One of the possible solutions might be closing this class against this kind of change by introducing polymorphic Query:

After doing that, if we run our test suite, it will be failing, probably, even with compile errors. That is not good because now we have to go through every failure and fix it, this will prevent us from continuously integrating for quite some time (half an hour, or a couple of days, depending on the impact of this change). And this has high chances of resulting in merge conflicts; that will impede work of others on our team.

Instead, let’s apply the parallel change.

Applying Parallel Change

Expand

First, we need to introduce a brand new method of our class (of course with unit-tests), without touching anything else:

At this point, we are going to deploy this new code.

Migrate

Second, we need to add a deprecation warning to the old interface, and, in fact, let’s rewrite old functions via the new one:

At this point, we are going to deploy this new code.

Next, we need to:

- inform all clients of our system about this deprecation and give them time-frame to migrate, or
- if all clients of `UserSearch` are under our control, we need to change all calls to use new interface:

After every line of change like that (big system probably has a multitude of these) we are going to deploy.

After every line of change like that we are going to deploy.

Of course, after each line we are going to deploy.

Contract

Once all our tests pass and there is no single deprecation warning from UserSearch, or the time-frame we have given to our 3rd party clients is finished, we can remove old functionality by just removing deprecated methods (and their unit-tests), and what we will have left is:

Of course, we can deploy our system now.

Bottom Line

Notice, how following this technique avoids even a single compile error or test suite failure. And if failure happens, the last small code change (probably one line) was wrong, you just CTRL+Z it to get back to GREEN state.

Was that refactoring necessary? Oh yeah, it was! Because in next few weeks, there were new requirements that would have forced us to add two new search_by_* methods to UserSearch class. Instead, we just created new derivatives of Query interface/protocol and used them in the places where they are needed. This way we were able to change how UserSearch class works without modifying its source code, by only adding new code. That is a great win.

Stay tuned!

Thanks!

Thank you for reading, my dear reader. If you liked it, please share this article on social networks and follow me on twitter: @waterlink000

If you have any questions or feedback for me, don’t hesitate to reach me out on Twitter: @waterlink000

This article originates from my blog: http://www.tddfellow.com

Oleksii Fedorov

Written by

Oleksii helps developers create applications in the Kotlin language. Grab his Ultimate Tutorial: Getting Started With Kotlin: https://iwillteachyoukotlin.com

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade