Refactoring is one of the cornerstones of the technical agile development practices. It is the mechanism that allows the design and architecture of a system to evolve over time. It is one third of the red-green-refactor loop and the core of test driven development (TDD). But does it really deliver on its promises? If you and your team are diligent writing tests and refactor mercilessly will your software evolve well and easily? Is the cost of refactoring always small and affordable? Refactoring is not a silver bullet, and sometimes is painful and expensive, so we cannot rely on always refactoring with a limited cost. However, any design, no matter how appropriate today, will be inappropriate tomorrow as the requirements change, and refactoring is our best tool to evolve our designs and architectures.
To "refactor" code means to change the structure of the code without changing its behavior. In all but the most simple of programs, this practically means that there are tests that act as a ‘safety-net' to let you know when your design changes have changed behavior. Refactoring cannot practically occur without tests. Therefore all teams that want to change/evolve their design must have automated tests to enable the refactoring.
So why refactor? In essence, we refactor to make things better. When the design no longer meets the problem at hand - whether it is because the requirements have changed or we have a better understanding of the problem - it helps to change the design to better match the problem. This makes the code more readable for others and therefore easier to understand, and readability is important since we spend much more time reading code than we do writing it.
Theory: Refactoring Leads to Good Design
Refactoring, as mainly used in the Agile community, is almost always found within the red-green-refactor loop of Test Driven Development (TDD). Write a failing test (red), make it pass (green), then clean up the code (refactor). It has been claimed that this simple process will lead to designs and architectures that evolve into just what you need. It has been claimed that if you build for only what is required for the requirement at hand that you will be able to add the missing functionality later by refactoring at a small cost. But is this necessarily true?
No it is not - not always. In fact, evolutionary designs and architectures can be just as bad if not worse than upfront designs and architectures. Teams - especially large ones - have a tendency to reinvent the wheel in different parts of the systems as different solutions to similar problems evolve.
Moreover, refactoring is a greedy algorithm, [i] which means that we make locally optimum choices - via YAGNI—You Ain't Gonna' Need It [ii]—with the hope of finding the global optimum. Greedy algorithms are notorious for getting stuck in local minima. Likewise, refactoring keeps your code and design clean - but not optimal. Every once in a while large refactorings need to be made, and they are not easy.
Large Refactorings are Hard
Large refactorings are significantly more difficult than the default refactorings that have been catalogued. They should not be underestimated. For example, retrofitting fine-grained security control in a system that was not designed with security in mind will be a time consuming and difficult task. But they can be done if a safety-net of tests is written for the application.
If, on the other hand, there were no tests then this type of retrofitting would be significantly more expensive (if not impossible). This is why many traditional applications - written without