(You’ll have to forgive me on this one. This post is kind of rambling stream-of-consciousness, but it’s been on my mind the last few days.)
I’ve been thinking a lot about refactoring the last few days, and about how the tools we use (and create) for refactoring are involved in the refactoring activity. Here are some random thoughts:
First, I’ve realized that refactoring is more than just applying the changes to code. It seems there are a few steps (and decisions) involved:
- Recognize or decide that a particular piece of code needs to be refactored.
- Choose a refactoring to be applied.
- Apply the refactoring.
Automated refactoring tools, including the ones that come with Castalia, are really good at making step #3 painless and accurate, but we only use them after we’ve gone through the mental effort of steps 1 and 2.
Unfortunately, generally speaking, these steps are in reverse order of difficulty. The truth is, it’s relatively easy to break a refactoring down into specific steps. Any decent programmer could follow the steps in Fowler’s book, or a better programmer could turn those steps into an automated tool.
It’s harder to know which refactoring to apply in a given case. Yes, knowing when to apply “Rename Local Variable” is easy, but it takes some experience to know when to use “Tease Apart Inheritance” or “Extract Hierarchy.”
So knowing which refactoring to apply in a given situation can be hard, but it gets worse: How do you recognize when you need to refactor in the first place? Step 1, recognizing the need for refactoring, requires the identification of “smells,” and that seems to only come with considerable experience.
So, recap: The first step in refactoring, recognizing that refactoring is needed, is the hardest part of the refactoring process. The next step, figuring out which refactoring to apply, can also be difficult. The third step, actually doing it, is considerably easier, and is made even easier by automated refactoring tools.
Sometimes, the recognition and decision steps are just confusing. Take, for example, a very long and complex function for drawing a control to the screen. It has painting code with device contexts and/or canvases. It has code for manipulating the data behind the control so that it can be drawn. It has code for drawing the control itself (borders, backgrounds, etc). It has code for drawing the graphical representation of the data. It has a lot of conditionals for drawing selected text or a selection rect or an insertion caret. This is one long method, and it does a lot of things.
We’ve all written code like this (it seems especially easy to do with drawing code for some reason).
Now, in examining the method, the first thing we do is notice that it “smells.” It’s too long. Some of you reading this will say “Well duh, obviously a method that’s that long needs to be fixed.” Others will say “Duh, obviously a method that’s doing that many different things needs to be fixed.” Others (who will never admit it) will say “What’s wrong with a really long method? It’s fine.”
Anyway, we’ve recognized the smell. We decide that it needs to be refactored. Step 1 accomplished.
Now, what do we do with it? Well, there are a number of refactorings that can help make a method shorter. Off of the top of my head, there’s “Inline Temporary Variable,” “Replace Temp With Query,” “Decompose Conditional,” and of course, “Extract Method.” Knowing which of these to apply takes experience, and knowing where to apply them takes quite a bit of effort as well (This is particularly true for Extract Method).
Once you’ve decided that what you need to do is Extract Method, and that you need lines 20-35 to become their own method, it’s trivial to select those lines and choose “Extract Method” from the Refactoring Menu. The tool does the rest.
I used to think that the tool was doing the hard part here. It was ensuring that your code’s behavior didn’t change. It was ensuring that the new method got inserted the right way with the right syntax. It was taking replacing what could be a few minutes to a few hours of tedium with a fast, painless process.
The curious thing is that with that problem solved, good refactoring takes less time, but it still seems to require more mental effort than it should.
So for someone who writes software that’s intended to help other programmers be more productive, this brings up a lot of interesting (and mostly rhetorical) questions:
- Can the tool help you recognize smells? (I say yes, and that’s what static code analysis is for)
- Can the tool help you decide which refactoring to apply? How does a tool know when a variable should be renamed? How does it know which part of an overly-long method should be extracted?
- If the tool can do any of these things, how should it present the information to the user?
These are just a few of the things that have been on my mind lately. I’d love to hear what you think.
P.S. This seems to be somehow related to the whole “discoverability” issue I wrote about here.
I’m the creator of Castalia
, a leading tool for Delphi programmers. Castalia can help you write code that needs less refactoring, and when you do need to refactor, Castalia makes it easy and fun. Check it out at http://www.twodesk.com/castalia