Tuesday, May 8, 2018

Refactor when you need it

The development cycle for Agile and TDD is simple:
  • Define a new requirement
  • Write a test for that requirement
  • Run the test (and see that it fails)
  • Change the code to make the test pass
  • Run the test (and see that it passes)
  • Refactor the code to make it clean
  • Run the test again (and see that it still passes)
Notice that refactor step near the end? That is what keeps your code clean. It allows you to write a messy solution quickly.

A working solution gives you a good understanding of the requirement, and its affect on the code. With that understanding, you can then improve the code, making it clear for other programmers. The test keeps your revised solutions correct -- if a cleanup change breaks a test, you have to fix the code.

But refactoring is not limited to after a change. You can refactor before a change.

Why would you do that? Why would you refactor before making any changes? After all, if your code is clean, it doesn't need to be refactored. It is already understandable and maintainable. So why refactor in advance?

It turns out that code is not always perfectly clean. Sometimes we stop refactoring early. Sometimes we think our refactoring is complete when it is not. Sometimes we have duplicate code, or poorly named functions, or overweight classes. And sometimes we are enlightened by a new requirement.

A new requirement can force us to look at the code from a different angle. We can see new patterns, or see opportunities for improvement that we failed to see earlier.

When that happens, we see new ways of organizing the code. Often, the new organization allows for an easy change to meet the requirement. We might refactor classes to hold data in a different arrangement (perhaps a dictionary instead of a list) or break large-ish blocks into smaller blocks.

In this situation, it is better to refactor the code before adding the new requirement. Instead of adding the new feature and refactoring, perform the operations in reverse sequence: refactor and then add the requirement. (Of course, you still test and you can still refactor at the end.) The full sequence is:
  • Define a new requirement
  • Write a test for that requirement
  • Run the test (and see that it fails)
  • Examine the code and identify improvements
  • Refactor the code (without adding the new requirement)
  • Run tests to verify that the code still works (skip the new test)
  • Change the code to make the test pass
  • Run the test (and see that it passes)
  • Refactor the code to make it clean
  • Run the test again (and see that it still passes)
I've added the new steps in bold.

Agile has taught us is to change our processes when the changes are beneficial. Changing the Agile process is part of that. You can refactor before making changes. You should refactor before making changes, when the refactoring will help you.