Workflows of Refactoring

Refactoring has grown into a well-known technique, and most software development teams at least claim to be doing refactoring regularly.

Many teams, however, don't appreciate the different workflows that refactoring can be used in, and thus miss opportunities to effectively incorporate refactoring into their development activities.

In this deck I explore various different workflows. I hope it will encourage teams to integrate refactoring more deeply into their work, resulting in a better designed code-bases that will make it quicker and easier to add new features.

8 January 2014

This page is a fallback page for the proper infodeck.

There are couple of reasons why you are seeing this page

The following is dump of the text in the deck to help search engines perform indexing

Many teams miss opportunities for refactoring by not realizing the different ways refactoring can fit into their workflows.
Workflows of Refactoring

Refactoring has grown into a well-known technique, and most software development teams at least claim to be doing refactoring regularly.

Many teams, however, don't appreciate the different workflows that refactoring can be used in, and thus miss opportunities to effectively incorporate refactoring into their development activities.

In this deck I explore various different workflows. I hope it will encourage teams to integrate refactoring more deeply into their work, resulting in a better designed code-bases that will make it quicker and easier to add new features.

Martin Fowler

2014-1-8

Hints for using this deck

My thanks to Sarah Taraporewalla, Pete Hodgson, Derek Hammer, Jonny Leroy, Manan Bharara, Danilo Sato, Khartik Krishnan, Kumar Iyer, Josh Kerievsky, and Micheal Feathers for commenting on drafts of this deck


Refactoring is often taught in the context of TDD

➊  Add a Test: Use specification by example by adding a test for yet-to-be-built functionality. This test will fail, making the test suite red.

➋ Make it work: Make the failing test pass (go green) by implementing the necessary functionality simply but crudely.

Test-Driven Development (TDD) is often described in terms of the red-green-refactor cycle

➌ Make it Clean: Use refactoring to ensure the overall code base is as clean and well-designed as possible for currently-implemented functionality.

Why have two distinct steps?

When we make a test work, why does the TDD cycle make separate steps for first making the test run and only then making the code clean?

To explain this, I'll introduce the metaphor of Two Hats…


The metaphor of Two Hats goes back to the earliest days of refactoring.

Refactoring

When refactoring every change you make is a small behavior-preserving change. You only refactor with green tests, and any test failing indicates a mistake. By stringing together a series of small changes like this you can move more quickly and with less risk because you shouldn't get trapped in debugging.

Adding Function

Any other change to the code is adding function. You will add new tests and break existing tests. You aren't confined to behavior-preserving changes (but it's wise to keep changes small and return to green tests swiftly).

During programming you may swap frequently between hats, perhaps every couple of minutes. But...

You can only wear one hat at a time
Separating refactoring keeps work focused

While making the test work, we can focus just on the problem of adding the new functionality, without thinking about how this functionality should be best structured.

Once things are working we can now concentrate on good design, while working in the safer refactoring mode of small steps on a green test base.

I call this kind of refactoring workflow:

TDD Refactoring

TDD refactoring is a common workflow for refactoring, but it isn't the only way that refactoring should be part of a programmer's day. The next few slides will explore some more ways of incorporating refactoring into a team's work.


Eww - this code's pretty awful

As we work with a code base, we'll often run into messy areas of the code base. Maybe they were written by a less capable developer, or by a usually capable developer on a bad day, or we didn't understand how to do it properly six months ago....

Whatever the reason, by cleaning up code as we work in it, we make things quicker for us the next time we need to work with it. Perhaps even make it quicker to change now.

This is often referred to as the boy-scout rule. Always leave the code better than when you found it.

Litter-Pickup Refactoring

What's this block of code doing?

It's squishing the fibbly-bar.

So should we extract it into a squishFibblyBar function?

Good programmers value code that is easy to understand, and thus cheap to use and modify. But clear code, like clear writing, is hard to do. Often you can only tell how to make it clear when someone else looks at it, or you come back to it at a later date.

Ward Cunningham explained it like this. Whenever you have to figure out what code is doing, you are building some understanding in your head. Once you've built it, you should move that understanding into the code so nobody has to build it from scratch in their head again.

(You could argue that since incomprehensible code is bad code, then comprehension refactoring is a form of litter-pickup. I think of them as different since the trigger is different.)

Comprehension Refactoring
Litter-Pickup and Comprehension Refactoring are opportunistic refactorings

For both of these you spot a problem while working on something else. Hence they share a common flow of actions.

People should be always on the look-out for substandard code. Often as a team learns, code written a few months ago can now seem lacking, and should be fixed.

Fixing right away is a good move if it's a simple fix, or if the fix will make it easier to add the feature you're currently working on.

It's important that refactoring is done on a stable code-base, that is with tests that are all green (passing). To get to a green state, you can use a version control system to stash current work, or otherwise temporarily disable any work-in-progress that's causing tests to fail.

Use refactoring to clean up the problematic code.

If the refactoring ends up being longer than is reasonable, stash the refactoring and come back to it later.

If the refactoring seems too much to do right now, or it's too awkward to stash the work-in-progress then make a note of the refactoring and work on it after finishing the feature.

Getting the feature finished is not enough to be done. You also have to have to ensure the code you touched is clean (or at least cleaner than when you found it).

Notice the separation of actions between the two hats. It's generally useful to separate your work in this way so that you focus better.


If only we'd built this class with this block of code factored out into a hook function, then this new feature would be easy.

Often you start working on adding new functionality and you realize the existing structures don't play well with what you're about to do.

In this situation it usually pays to begin by refactoring the existing code into the shape you now know is the right shape for what you're about to do.

By making this change with your refactoring hat on, you can make the new functionality change much easier. Often this pays off to the the level that the overall change is faster than if you tried to add the change without the preparation.

A common analogy for this is prep work before painting. Scraping the surface and taping the edges isn't doing the painting, but often makes the actual painting go faster - and results in a longer lasting paint job.

Preparatory Refactoring
Preparatory Refactoring can be done when starting a new task

We'll add refactoring the flibbler interface to the plan

Many teams schedule refactoring as part of their planned work, using a mechanism such as "refactoring stories". Teams use these to fix larger areas on problematic code that need dedicated attention.

Planned refactoring is a necessary element of most teams' approach - however it's also a sign that the team hasn't done enough refactoring using the other workflows. If most of your refactoring is occurring only in planned work, then that's a bad smell - you should consider how to incorporate the other refactoring workflows into your work.

Planned Refactoring

We really need to sort out these dependencies, but it will take a couple of months

Some restructuring requires bigger changes than can be done in a single development episode - spanning multiple iterations over several months. Examples might include replacing a large module, changing your database persistence framework, or untangling some dependencies.

This large-scale restructuring can still be done using refactoring. The team needs to agree on a rough end-state as well as a rough plan to get there. Then during their regular work they take the opportunity to carry out refactorings that move the architecture towards the desired direction. Since all changes are refactorings, the code base can remain in a working state even as features are added.

One common technique for long-term refactoring is Branch By Abstraction, where you use an abstraction layer that supports the current and a replacement implementation.

Long-Term Refactoring
Is refactoring wasteful rework?

Different programmers have different approaches, so will some programmer rewrite perfectly good code just due to differences in taste?

Is refactoring just making the code look pretty?

Remember the economic justification

Refactoring makes it easier to understand the code - which makes subsequent changes quicker and cheaper.

Preparatory refactoring can pay for itself when adding the feature you're preparing for.

Don't refactor unless you think you will recoup your investment later by quicker work

Balance refactoring with feature delivery

Refactoring should be done in conjunction with adding new features.

Use refactoring to make things cleaner, but don't try to completely fix things. The key is gradual improvement through many passes through the codebase


To use refactoring in its most effective way, you need to combine all the refactoring workflows so that they flow seamlessly into your development work.

If your code base is difficult to understand and modify, it's slowing you down - and that's a sign that you need to do more refactoring in order to improve your efficiency. For most teams this needs more effort into the day-to-day refactoring workflows in order to introduce steady improvement.

Refactoring ➔ Clean Code ➔ Faster Delivery