← Back

What I Talk About When I Talk About Refactoring

Refactoring gradually improves the internal design of software and, together with testing, helps build applications that are easier to understand, maintain, and evolve.

Refactoring

I want to clarify a few terms.

When we start a new software project, whatever kind of application it may be, we rarely know exactly how we are going to implement it. I mean what the class structure will look like, the best way to organize the project assets, what interfaces will emerge, or even what design and architecture decisions we will need to make. At most, we have a rough idea; we do not yet know the complete and exact shape the solution will take.

It is true that, with time and experience, you begin to have a clearer idea before you start, depending on the kind of project and application.

So, in reality, we will not know the best way to do things until we start implementing functionality. It is another version of the saying, “Traveler, there is no path; the path is made by walking.”

I can assure you of this universal law: the first attempt is never the best.

The worst thing that can happen to an application is for it to remain exactly as it was in its initial implementation, without refining or improving any part of its structure or design. And when I say application, we can zoom in to a library, module, class, or method: the principle still applies.

Building a quality application is a process of continuous, incremental improvement, and in reality, there will always be something left to improve.

Things get more complicated as we add more and more functionality, sometimes including things we never expected or imagined. That is perfectly natural in software development, but the worst mistake we can make is to add that functionality to the existing skeleton any old way. Over time, we will be laying the groundwork for an unmaintainable application that is difficult to understand and evolve.

Refactoring is essential to avoid this situation. When we talk about refactoring, we specifically mean applying certain techniques that improve the micro-design of the code we have written. They are small steps, each of which improves a tiny aspect of the solution.

By extension, we also use refactoring as a synonym for improving any element of an application. It is fine to extend the concept, even though it originally refers exclusively to applying specific, well-documented techniques to improve code. That is why, although it is not entirely precise, we can still understand what someone means when they talk about refactoring a database design, a script, or something else.

But why refactor if a piece of code, or the whole solution, “already works”?

Every software solution should be designed to accommodate change easily, because there is a 99% probability that change will happen. Refactoring therefore gives us an application with better design and structure, one that is easier to understand and maintain.

There are also subtle aspects that we only learn through experience: refactoring makes our code easier to test, but the reverse is also true—our desire to write tests leads us to the need to refactor, consciously or unconsciously. This is one of the aspects of software development I like most, because many characteristics of code are subtle and take time to recognize.

Refactoring goes hand in hand with two concepts, and is so closely intertwined with them that it is impossible to talk about one without the others: object orientation and testing.

An application written in an object-oriented language can have a better structure and provide a better solution because it lets us abstract a problem into mental entities that are easier for us to understand: objects. Testing, in turn, is an unavoidable requirement for ensuring the application’s quality and correct behavior.

If we change anything in the code, no matter how small, how do we know everything still works? Exactly: with tests.

So applying refactoring techniques to an application that is not sufficiently covered and supported by tests is impossible, or even dangerous.

In software development, it is not good practice to leave improvements until the end. It is more productive to incorporate every improvement into our day-to-day work as part of the development process itself. That is what the theory says, and I have also verified it myself over all these years. Continuously improving code undoubtedly helps us develop faster.

In other words, refactoring techniques are applied continuously and incrementally. It is surprising how tiny changes to an application can have an enormous, cumulative effect on the final product.

Many techniques have been documented since the concept appeared in Martin Fowler and Kent Beck’s book, and various authors have proposed many others since our industry accepted that code should be improved continuously, in small incremental steps.

Even an application that has never been refactored in this way and does not have good test coverage can be improved, even if we have to start its refactoring from scratch and proceed little by little.

If you want to learn more about refactoring, I describe the most common techniques in my latest work, The Practical Agile Programmer, along with countless essential practices for creating quality code.