Refactoring On Purpose
If you ask a bunch of software developers about code smells, refactoring code, working with legacy code, paying off technical debt, and remediating or “rescuing” existing code, you’ll hear some different nuances but on the whole they will tell you things like:
- code smells are characteristics of source code that indicate design improvements may be possible
- refactoring means changing the structure of code without changing its behavior
- legacy code is code that doesn’t have any executable test cases covering it
- technical debt results from changing code in a hurry, without taking the time to re-think the design
- we can “pay off” or “remediate” technical debt by refactoring the code
- refactoring is best done incrementally, in the course of making changes to the code, rather than stopping value-add work to carry out a “big bang” refactoring effort
It may seem as if technical professionals have come to general agreement about something. That in itself should make you suspicious.
Drilling Down
If we take the questions to a lower level of detail, the answers become more interesting.
- Which code smells do you encounter most often in existing code bases?
- Which refactorings do you tend to use most often when remediating legacy code?
- Tell me a story from your experience of a time when technical debt was incurred intentionally as a way to achieve a tactical goal, based on an economic analysis of available options, and was later repaid according to plan?
I’ve learned a few things about the current state of the art in software development by asking questions like these.
Almost all developers can recite the standard buzzwords and the canonical definitions around these concepts. But…
A surprising number of developers don’t know there are specific code smells that have names, and specific refactorings that have names and that are designed to address specific code smells in a way that doesn’t break things. Most developers figure anything they don’t like is a “code smell” and any change they care to make is a “refactoring.”
A surprising number of developers never practice refactoring code, in the same way as a musician practices scales or a martial artist practices forms. The only refactoring they have ever done was on real code, at work, under time pressure. They only ever even try to refactor real code at work and under time pressure.
Most people are comfortable with the metaphor of financial debt as applied to software, but almost no one has a real-world, experience-based story of a case when a team made an economically-based decision to incur technical debt intentionally, with a plan to repay that debt, and actually repaid it.
Drilling Deeper
What happens if we take the questions to an even lower level of detail?
Study this code sample for a few minutes. Okay? Now…
- What specific code smells do you see in this code?
- Which of those do you think are significant problems?
- Given a limited amount of time to remediate this code, which problem would you address first, and why?
- How would you approach that remediation, and what is your rationale for that approach?
I’ve learned a few things about the current state of the art in software development by asking questions like these. <sigh/>
A surprising number of developers seem to have nothing in mind when they examine a code base. They react impulsively to anything in the code that they would have written differently. It’s rather amazing how many developers can stare at crufty code for several minutes and never perceive the code smells.
Even those who do recognize specific code smells often seem unable to judge which ones are really worth remediating and which ones can safely be left alone.
Some people start deleting code or modifying APIs without (apparently) any thought about clients that may exist out in the world, and that would be broken if the API changed (see Hyrum’s Law). Others start by deleting unit tests instead of running them. Others do run the tests, but don’t examine the output to look for clues about where the high-impact problems might be (such as a long-running test case, for instance).
Why Practice?
You know all those repetitious patterns you practice on your favorite musical instrument, from material like the Arban book? You know, like this:
What’s the use of that? You might protest that no specific piece of music is identical to a series of repetitious patterns. Yes, that is technically true. Is it usefully true?
It’s true that no piece of music is identical to a practice pattern. But it’s also true that many pieces of music resemble the general shape of one or more patterns. If you’ve mastered the patterns, you can learn most pieces of music relatively quickly and play them more skilfully than if you had to learn every piece of music note by note.
You know all those slow, fluid movements you make when you practice a Tai Chi form? Every step is a self-defense move, if performed at full speed. For example, here’s an explanation of the martial application of Repulse the Monkey.
What’s the use of that? You might protest that no attacker in the street will follow the exact sequence of steps of any martial arts form. Yes, that is technically true. Is it usefully true?
The attackers will be human. As such, their bodies will move in ways that the architecture of the human body allows. They will not follow any particular martial arts form, but the general shape of their movements will be broadly similar to movements in forms.
To sharpen our skills as software developers, we often practice so-called “code katas.” For example, here’s a description of the Prime Factors Kata. Notice that it isn’t a random hack. We follow the same sequence of steps every time we practice this kata.
What’s the use of that? You might protest that no software requirement we’re likely to implement at work will call for calculating prime factors. Yes, that is technically true. Is it usefully true?
Different code katas represent various types of programming problems. Mastering several of these is good preparation for real work in the same way that mastering patterns on an instrument or forms in martial arts is good preparation for real-world situations: Many programming problems have similar “shapes.”
Why Not Practice Refactoring, Too?
We spend far more time working with existing code bases than we do writing clean, fresh code. Many of us invest some of our personal time to practice the craft of software development by working code katas. Why not allocate some of that time to practice the craft of refactoring?
In principle, this should be straightforward. Code smells are structural patterns in source code. Refactorings are specific modifications that address code smells. Couldn’t we practice refactoring just as we practicie musical patterns or sequences of martial arts forms?
An internet search will find numerous exercises billed as “refactoring katas,” but they aren’t exactly katas because they don’t prescribe any sequence of actions. Even if they don’t quite meet the definition of kata, most of them are useful exercises to practice. Here are a few examples.
Gilded Rose
One that has become somewhat famous is called Gilded Rose, devised by Terry Hughes. It sets up a situation with existing code, describes a new feature that is desired, and defines some constraints we have to adhere to. It’s a very good exercise, even if it may have suffered a little from too much popularity.
The code smell illustrated in the Gilded Rose code is complex conditional, and the main refactoring to use to address it is decompose conditional, which basically consists of a series of extract method refactorings. Both the situation and the remedy are very common in existing code bases everywhere, making Gilded Rose a practical choice for practicing.
Ode to Code
Michael Whelan posted sample code for a refactoring exercise by Scott Allen that he calls Ode to Code, and published a description on his blog. This code exhibits problems that are very typical in existing code bases.
Java Legacy Exercise
There’s a Java-based exercise we use for screening technical coaches at LeadingAgile that can be used for practice. The starter code presents a portion of an application that has quite a few code smells baked in. If you take a look at the sample solution, you may find some sequences of refactorings that would be useful to repeat as a practice exercise to deal with particular code smells.
Keyboard Shortcut Katas
A couple of months ago I decided to make IntelliJ IDEA my default Java IDE. Coming from an Eclipse background, I wanted to get the keyboard shortcuts under my fingers quickly. To help myself do that, I came up with a few exercises or katas for practicing keyboard shortcuts.
Some of them are refactoring katas. The goal for me was to practice the keyboard shortcuts associated with performing these refactorings, but you can take them as general refactoring katas, too. They include several simple refactorings, like rename, extract constant, extract field, extract variable, and two flavors of extract method. Also included is a decompose conditional refactoring kata. That is probably the most common multi-step refactoring we use in the field, as the most common code smell tends to be complex conditional.
No doubt you can find many more useful katas to help you practice refactoring.