Refactoring Chapter 2 — Principles in Refactoring (Part III)

Rafael Melo
5 min readJan 6, 2020

--

Refactoring, Architecture and YAGNI

Refactoring can improve the design of existing code, as this book’s subtitle implies. With refactoring, instead of speculating on what flexibility I will need in the future and what mechanisms will best enable that, I build software that solves only the currently understood needs, but I make this software excellently designed for those needs. As my understanding of the users’ needs changes, I use refactoring to adapt the architecture to those new demands.

This approach to design goes under various names: simple design, incremental design, or YAGNI. (originally an acronym for “you aren’t going to need it”). I think of yagni as a different style of incorporating architecture and design into the development process — a style that isn’t credible without the foundation of refactoring.

All this has led to a growing discipline of evolutionary architecture where architects explore the patterns and practices that take advantage of our ability to iterate over architectural decisions.

Refactoring and the Wider Software Development Process

To refactor on a team, it’s important that each member can refactor when they need to without interfering with others’ work. This is why I encourage Continuous Integration, where each member’s refactoring efforts are quickly shared with their colleagues. No one ends up building new work on interfaces that are being removed, and if the refactoring is going to cause a problem with someone else’s work, we know about this quickly. Self-­testing code is also a key element of Continuous Integration, so there is a strong synergy between the three practices of self-­testing code, continuous integration, and refactoring.

With this trio of practices in place, we enable the YAGNI design approach that I talked about in the previous section. Balance these practices, and you can get into a virtuous circle with a code base that responds rapidly to changing needs and is reliable. With a firm technical foundation, we can drastically reduce the time it takes to get a good idea into production code, allowing us to better serve our customers. Furthermore, these practices increase the reliability of our software, with less bugs to spend time fixing.

Keep in mind that software development, whatever the approach, is a tricky business, with complex interactions between people and machines. The approach I describe here is a proven way to handle this complexity, but like any approach, it requires practice and skill.

Refactoring and Performance

To make the software easier to understand, I often make changes that will cause the program to run slower. This can certainly make software go more slowly — but it also makes the software easier to performance tuning. The secret to fast software, in all but hard real­time contexts, is to write tunable software first and then tune it for sufficient speed.

In a good approach to performance, I build my program in a well-­factored manner without paying attention to performance until I begin a deliberate performance optimization exercise.

I begin by running the program under a profiler that monitors the program and tells me where it is consuming time and space. This way I can find that small part of the program where the performance hot spots lie. As in refactoring, I make the changes in small steps. After each step I compile, test, and rerun the profiler. If I haven’t improved performance, I back out the change. I continue the process of finding and removing hot spots until I get the performance that satisfies my users.

With well­-factored code, I can add functionality more quickly. This gives me more time to focus on performance, and also I have finer granularity for my performance analysis. My profiler leads me to smaller parts of the code, which are easier to tune. With clearer code, I have a better understanding of my options and of what kind of tuning will work.

Automated Refactorings

Perhaps the biggest change to refactoring in the last decade or so is the availability of tools that support automated refactoring. It’s now pretty common to find some kind of refactoring support in editors and tools, although the actual capabilities vary a fair bit. Some of this variation is due to the tool, some is caused by the limitations of what you can do with automated refactoring in different languages.

A crude way to automate a refactoring is to do text manipulation, such as a search/replace to change a name, or some simple reorganizing of code. This is a very crude approach that certainly can’t be trusted without rerunning tests. It can, however, be a handy first step.

To do refactoring properly, the tool has to operate on the syntax tree of the code, not on the text. Manipulating the syntax tree is much more reliable to preserve what the code is doing.

Refactoring isn’t just understanding and updating the syntax tree. The tool also needs to figure out how to rerender the code into text back in the editor view. All in all, implementing decent refactoring is a challenging programming exercise — one that I’m mostly unaware of as I use the tools.

Many refactorings are made much safer when applied in a language with static typing. Since the tool can resolve the method to the correct class with static typing, I can be confident that the tool changes only the methods it ought to.

Tools often go further. If I rename a variable, I can be prompted for changes to comments that use that name. If I extract a function the tool spots some code that duplicates the new function’s body and offers to replace it with a call. Programming with powerful refactorings like this is a compelling reason to use an IDE rather than stick with a familiar text editor.

While sophisticated refactoring tools are almost magical in their ability to safely refactor code, there are some edge cases where they slip up. So even with mostly safe refactorings, it’s wise to run the test suite every so often to ensure nothing has gone bad.

Going Further

This book has taught refactoring to many people, but I have focused more on a refactoring reference than on taking readers through the learning process. If you are looking for such a book, I suggest Bill Wake’s Refactoring Workbook that contains many exercises to practice refactoring.

Many of those who pioneered refactoring were also active in the software patterns community. Josh Kerievsky tied these two worlds closely together with Refactoring to Patterns.

Refactoring also applies in specialized areas. Two that have got useful attention are Refactoring Databases (by Scott Ambler and Pramod Sadalage) and Refactoring HTML (by Elliotte Rusty Harold). Also two of my former colleagues, Jay Fields and Shane Harvey, did this for the Ruby programming language.

Michael Feathers’s Working Effectively with Legacy Code, is primarily a book about how to think about refactoring an older codebase with poor test coverage.

For more up­-to-­date material, look up the web representation of this book, as well as the main refactoring web site: refactoring.com

--

--