For most developers, refactoring isn’t their favorite part of the job. Even Nicholas Thapen, the founder and CTO of Sourcery, which creates automated refactoring software, has had a few experiences with refactoring gone wrong. Thapen once worked for a company that spent two years trying to refactor a significant chunk of its codebase. After that experience, he learned the smaller the scope of refactoring, the better.
“You got to the end and you hadn’t changed the external behavior,” he said. “It was unclear the value that [refactoring] was providing ... and then everyone had to relearn how [the code] worked.”
What Is Code Refactoring?
Refactoring is the process of improving code without making any functional changes. For a given method, that means the inputs and outputs stay the same but the code inside can be changed. The final product still behaves the same way to the users, but the code will have been cleaned up or upgraded.
The way refactoring is conducted is important because it’s easy for developers to accidentally take on more than they can handle. Still, refactoring is an important step in the software development process.
A few developers shared their refactoring best practices and how they prevent this process from getting lost in a fast-paced software development workplace.
A Few Simple Techniques for Refactoring
Brendan McCollam, engineering manager at tech recruitment platform Codility, said refactoring techniques come down to two general ideas: breaking down complicated code into smaller parts or consolidating code into reusable components.
He gave the example of an application that implements calendar date displays in multiple places. That’s not ideal because different implementations can lead to inconsistencies for users and developers. Refactoring that code would consist of consolidating all those implementations into a single helper function.
“So instead of having a bunch of different places where you’re doing that, you have one date-time module where your application does all of its logic,” he said.
Another refactoring technique is renaming functions and classes from something generic to something self-explanatory. That type of change may allow developers to get rid of existing comments that describe what the functions and classes do.
“Comments can be useful but it’s usually better to have really good names for everything rather than rely on comments,” Thapen said. “Comments can age and degrade — they may no longer be attached to the right bit of code, they may describe what it used to do rather than what it now does.”
Readability Is the Goal of Refactoring
Developers actually spend more time reading code than writing new code, Thapen said. That’s why a major benefit of refactoring is cleaning up code by extracting snippets into their own functions, making code more readable.
“You’re spending maybe 80 percent of your time reading, understanding what’s already there in order to change it,” he said. “That’s why the readability is so important — it really affects the velocity of adding new features.”
Rodrigo Pimentel, director of engineering at chat feed provider Stream, said readability should be a top priority during refactoring.
“If everyone’s always annoyed or angry when they have to work on that, then that’s a good sign it should change,” Pimentel said. “One of the main reasons for a refactor is so people can develop around that codebase more easily.”
“If everyone’s always annoyed or angry when they have to work on that, then that’s a good sign it should change.”
Code that feels too complex or long can usually also use a refactor. Although that benchmark is subjective, Thapen said a good indicator is if developers have to scroll a lot to understand a single function or class.
Nested code is another indicator — nested functions aren’t individually long, but the logic can be difficult to trace, making it difficult to read. Not only is complex code hard to understand, but it also likely doesn’t follow good principles of modularity. That means the logic can become interdependent, making the code changes unusually brittle.
“If you’re trying to make a simple change somewhere and it turns out you have to change every other bit of a system to make that change, it’s a sign that you might need to refactor,” Thapen said.
Does Your Code Really Need to Be Refactored?
It’s important to gauge whether your existing codebase truly needs a refactor because there can be negative consequences if it goes wrong. Even if a piece of code is badly written it doesn’t necessarily need refactoring, Thapen said. That’s because code that runs well and doesn’t get updated won’t cause developers any new problems.
Existing codebases should only be refactored if the code needs to be changed fairly often and the changes seem to always cause problems. At that point, developers should determine the extent of the refactor.
The best way to do that is by reading code, said Shlomi Vaknin, founding engineer at customer relationship management platform DevRev. Developers who educate themselves by reading a lot of code at their company can develop an intuition for how to refactor well.
“It’s very important to learn about what code looks like and to develop your personal sense of code elegance,” Vaknin said. “That will get you a gut feeling of what kind of code you can easily follow — what you consider to be good code, readable code, maintainable code.”
Refactoring Is Great for Agile Development
Back when software development followed waterfall methodology, and entire software projects were fully designed prior to the start of coding, refactoring was seen as something developers did only when software was badly designed, Vaknin said.
“In the old days, this was frowned upon,” he said. “[The idea was] if you refactor code, you must not have spent a lot of time designing.”
But today refactoring is seen as a “natural and mature” process of software development, Vaknin said. “Code is alive.”
Refactoring is essential to agile development, which follows the principle of continuous improvement. It allows developers to focus on functionality rather than optimization, giving them permission to write the important code without worrying too much about making it perfect — they can always come back later to polish it up.
“If you’re trying to start by writing really efficient code, you’ll often write bad code.”
“Premature optimization is the root of all evil,” Thapen said. “If you’re trying to start by writing really efficient code, you’ll often write bad code. You spend a lot of time making it efficient — most code doesn’t need to run that fast.”
Refactoring prevents bottlenecks by giving developers room to break projects into separate pieces that are all continuously improved. Pimentel said companies even have the option of doing “dark deploys,” where refactoring updates are pushed out to production without any formal notifications after the initial unoptimized code is already deployed. The method works because the functionality of the code isn’t changed during refactoring.
Dark deploys are usually used “if it’s something that you’re doing that needs to be released soon — maybe a fix,” Pimentel said.
Agile methodology encourages developers to be refactoring constantly, as a project grows and takes shape, rather than at the end when all the code is written.
“I feel like I’m doing it on an hourly basis,” Thapen said. “On this principle, a lot of teams say to leave the code a bit cleaner than when you found it.”
Leave Room for Refactoring During Sprint Planning
Refactoring significant chunks of existing code works best when time is specifically reserved for it within sprints. Pimentel said refactoring should be scoped just like any other aspect of development.
“It has to have a very well-defined, fixed and as-contained-as-possible scope,” Pimentel said. “I have seen larger refactors that took many, many days and had been abandoned because at some point you had to go back to developing something else and there was no end in sight.”
Developers should determine the scope of the refactoring work before they start, then reassess the situation based on new knowledge gained from refactoring. If the scope increases dramatically it may be better to abandon it early.
Refactoring work that has a small scope will probably be successful because developers are likely only refactoring their own, recently written code. As the scope widens, repercussions on the codebase could begin affecting other developers.
“It’s dangerous if somebody just goes off, locks themselves in a room for a week and comes back and says, ‘Look, I’ve refactored everything,’” McCollam said. “You want to pause other changes or at least have other people aware that the refactor is coming, so that they can think about how they’re going to build things.”
If other developers don’t know about ongoing changes, problems like duplication of work can occur. That wastes time for all developers involved.
The longer a refactor takes, the higher the risk for those sorts of conflicts. When that happens, developers sometimes decide the best course of action is to abandon all the refactoring work done up to that point. It often doesn’t make sense to leave the code in what McCollam calls an “in-between state.” If a development team was changing its application’s coding style from functional programming to object-oriented programming, for example, there wouldn’t really be the option of leaving the refactoring work halfway in between.
“You just have these two ugly pieces that don’t fit together, and that’s the worst of both worlds,” McCollam said. “You would have been better off not doing that partial refactor because you’ve taken a coherent whole and made it into mismatched pieces — and now somebody else who’s coming along and working on it doesn’t know which style they should be following,”
Development teams can get ahead of the problem by designating time for refactoring, Pimentel said. Refactoring work should be tracked with tickets just like normal development — otherwise, the team’s metrics can be thrown off and lead to a cascading series of problems. If a team only tracks feature development and expects to squeeze in refactoring on top of two weeks’ worth of features, the team will probably not complete all their tasks for the sprint.
“It’s a sign of a healthy engineering culture, where you are allowed and sometimes even encouraged to refactor code, not only churn out features,” Pimentel said.
Having a Good Test Suite Is Crucial
Developers agree on the importance of testing when doing refactoring. The functionality of code shouldn’t change after refactoring, so tests are a perfect way of making sure that’s true. Ideally, test suites should be as comprehensive as possible.
“If you don’t have a good test suite, it’s almost like stepping out on a rickety wooden bridge — you want to step one step at a time and test each step as you go,” McCollam said. “If you have a test suite, then you can tear out all of the underpinnings and rebuild it. As long as the tests still pass, you know that everything still works.”
It’s also a good idea to update tests as the refactoring progresses. Developers may be adding additional code that doesn’t affect the functionality but still needs to be tested. Additional tests can also help future developers if the codebase ever needs to be refactored again.
And as a last line of defense, the proper use of version control can be a simple way to contain any damage done by complications from refactoring.
“Committing [the code] often really, really helps,” Pimentel said.