Refactoring Chapter 2 — Principles in Refactoring (Part I)

Rafael Melo
7 min readJan 6, 2020

--

Refactoring involve carrying out code manipulations that don’t change the overall functionality of the program and is always done to make the code “easier to understand and cheaper to modify.”

Each individual refactoring is either pretty small itself or a combination of small steps. As a result, when I’m refactoring, my code doesn’t spend much time in a broken state, allowing me to stop at any moment even if I haven’t finished. (If someone says their code was broken for a couple of days while they are refactoring, you can be pretty sure they were not really refactoring).

In my definitions, I use the phrase “observable behavior.” This is a deliberately loose term, indicating that the code should do just the same things it did before I started. It doesn’t mean it will work exactly the same. Any bugs that I notice during refactoring should still be present after refactoring (though I can fix latent bugs that nobody has observed yet).

The Two Hats

When I use refactoring to develop software, I divide my time between two distinct activities: adding functionality and refactoring.

  • When I add functionality, I shouldn’t be changing existing code; I’m just adding new capabilities.
  • When I refactor, I make a point of not adding functionality; I only restructure the code.

As I develop software, I find myself swapping hats frequently. I start by trying to add a new capability, then I realise this would be much easier if the code were structured differently. So I swap hats and refactor for a while. Once the code is better structured, I swap hats back and add the new capability. Once I get the new capability working, I realise I coded it in a way that’s awkward to understand, so I swap hats again and refactor. All this might take only ten minutes, but during this time I’m always aware of which hat I’m wearing and the subtle difference that makes to how I program.

Why Should We Refactor?

Refactoring is no “silver bullet.” It is a tool that should be used for several purposes.

REFACTORING IMPROVES THE DESIGN OF SOFTWARE

The internal design — the architecture — of software tends to decay. As people change code to achieve short-­term goals, often without a full comprehension of the architecture, the code loses its structure. Loss of the structure of code has a cumulative effect. The harder it is to see the design in the code, the harder it is for me to preserve it, and the more rapidly it decays. Regular refactoring helps keep the code in shape.

REFACTORING MAKES SOFTWARE EASIER TO UNDERSTAND

Programming is in many ways a conversation with a computer. I write code that tells the computer what to do, and it responds by doing exactly what I tell it. But there are likely to be other users of my source code. In a few months, a human will try to read my code to make some changes.

Who cares if the computer takes a few more cycles to compile something? Yet it does matter if it takes a programmer a week to make a change that would have taken only an hour with proper understanding of my code.

I’m not necessarily being altruistic about this. Often, this future developer is myself. This makes refactoring even more important. I’m a very lazy programmer. One of my forms of laziness is that I never remember things about the code I write. I make a point of trying to put everything I should remember into the code so I don’t have to remember it.

REFACTORING HELPS ME FINDING BUGS

Help in understanding the code also means help in spotting bugs. I admit I’m not very good at finding bugs. However, I find that if I refactor code, I work deeply on understanding what the code does, and I put that new understanding right back into the code. By clarifying the structure of the program, I clarify certain assumptions I’ve made — to a point where even I can’t avoid spotting the bugs.

REFACTORING HELPS PROGRAM FASTER

When I talk to software developers who have been working on a system for a while, I often hear that they were able to make progress rapidly at first, but now it takes much longer to add new features. Every new feature requires more and more time to understand how to fit it into the existing code base, and once it’s added, bugs often crop up that take even longer to fix.

But software with a good internal design allows me to easily find how and where I need to make changes to add a new feature. If the code is clear, I’m less likely to introduce a bug, and if I do, the debugging effort is much easier.

Since it is very difficult to do a good design up front, refactoring becomes vital to achieving that virtuous path of rapid functionality.

When Should We Refactor?

PREPARATORY REFACTORING

The best time to refactor is just before I need to add a new feature to the code base. As I do this, I look at the existing code and see that if it were structured a little differently, my work would be much easier.

Perhaps there’s a function that does almost all that I need, but has some literal values that conflict with my needs. Without refactoring I might copy the function and change those values. But that leads to duplicated code. The same happens when fixing a bug. By refactoring to improve the situation, I also increase the chances that the bug will stay fixed, and reduce the chances that others will appear in the same parts of the code.

COMPREHENSION REFACTORING

Before I can change some code, I need to understand what it does. This code may have been written by me or by someone else. Whenever I have to think to understand what the code is doing, I ask myself if I can refactor the code to make that understanding more immediately apparent.

I rename a couple variables now that I understand what they are, or I chop a long function into smaller parts. Then, as the code gets clearer, I find I can see things about the design that I could not see before. When I’m studying code, refactoring leads me to higher levels of understanding that I would otherwise miss.

LITTER-PICKUP REFACTORING

This happens when I understand what the code is doing, but realise that it’s doing it badly. If it’s easy to change, I’ll do it right away. If it’s a bit more effort to fix, I might make a note of it and fix it when I’m done with my immediate task.

Sometimes, of course, it’s going to take a few hours to fix, and I have more urgent things to do. Even then, however, it’s usually worthwhile to stop it and make it a little bit better.

PLANNED AND OPPORTUNISTIC REFACTORING

Refactoring isn’t an activity that’s separated from programming — any more than you set aside time to write if statements. I don’t put time on my plans to do refactoring; most refactoring happens while I’m doing other things. Whether I’m adding a feature or fixing a bug, refactoring helps me do the immediate task and also sets me up to make future work easier.

Whenever I write code, I’m making tradeoffs. The tradeoffs I made correctly for yesterday’s feature set may no longer be the right ones for the new features I’m adding today. The advantage is that clean code is easier to refactor when I need to change those tradeoffs to reflect the new reality.

If a team has neglected refactoring, it often needs dedicated time to get their code base into a better state for new features, and a week spent refactoring now can repay itself over the next couple of months.

Sometimes, even with regular refactoring I’ll see a problem area grow to the point when it needs some concerted effort to fix. But such planned refactoring episodes should be rare.

LONG-TERM REFACTORING

Most refactoring can be completed within a few minutes — hours at most. But there are some larger refactoring efforts that can take a team weeks to complete. Perhaps they need to replace an existing library with a new one, or something big like this.

Often, a useful strategy is to agree to gradually work on the problem over the course of the next few weeks. Whenever anyone goes near any code that’s in the refactoring zone, they move it a little way in the direction they want to improve. This takes advantage of the fact that refactoring doesn’t break the code — each small change leaves everything in a still ­working state.

REFACTORING IN A CODE REVIEW

It depends on the nature of the review. The common pull request model, where a reviewer looks at code without the original author, doesn’t work too well. It’s better to have the original author of the code present because the author can provide context on the code and fully appreciate the reviewers’ intentions for their changes. I’ve had my best experiences with this by sitting one-­on-one with the original author, going through the code and refactoring as we go. The logical conclusion of this style is pair programming: continuous code review embedded within the process of programming.

WHAT DO I TELL MY MANAGER?

I’ve certainly seen places were refactoring has become a dirty word — with managers believing that refactoring is correcting errors made earlier. To a manager who is genuinely savvy about technology and understands the design stamina hypothesis, refactoring isn’t hard to justify.

Of course, many managers don’t have the technical awareness to know how code base health impacts productivity. In these cases I give my more controversial advice: Don’t tell! A schedule­-driven manager wants me to do things the fastest way I can; how I do it is my responsibility. I’m being paid for my expertise in programming new capabilities fast, and the fastest way is by refactoring — therefore I refactor.

WHEN SHOULD I NOT REFACTOR?

If I run across code that is a mess, but I don’t need to modify it, then I don’t need to refactor it.

Often, I can’t tell how easy it is to refactor some code unless I spend some time trying and thus get a sense of how difficult it is. The decision to refactor or rewrite requires good judgment and experience, and I can’t really boil it down into a piece of simple advice.

End of Part I, continue on Part II

Go back to Index

--

--