So Justin Searls wrote a great post the other day on The Failures of “Intro to TDD”, and then Bob Martin wrote a great counterpoint to it. Both the posts are well worth reading, though these days I find that the latter of the two is closer to my own practice. But what I really want to talk about is Angela Harms’s response, calling attention to the way Bob Martin’s article is shaped by working in languages with strong refactoring tools:
This got me thinking! See, I recently experienced this contrast firsthand. From 2011 to 2012, I got to see what changed in the experience of working on a large Java project while we introduced Groovy into the code base – consciously trading in easier refactoring for a more flexible and dynamic language. I don’t think it made us more afraid of refactoring, but I do think it took conscious effort to keep the habit up as we lost tool support for it. I thought I’d take a few minutes this evening to think through what helped:
Practice the Fundamentals
Laura and I were actively preparing to run a workshop on refactoring techniques, meant to be accessible even to those without great tooling. This meant we were spending a lot of time running through several of the key refactorings by hand, step by step. As George Dinwiddie said in that Twitter thread:
Pick Your Battles
In Java, it was easier to dive into big refactoring efforts that would juggle around code from dozens of different classes. In Groovy, we’d concentrate our efforts on places where we could make relatively small changes with big structural significance. Frankly, I think this was much better for the overall quality of our code.
Give It Time
In Java, when we migrated from one way of doing things to another, we wouldn’t be satisfied until it was completely done. In Groovy, we got a lot more willing to let both approaches live side by side for a little while. If we changed a method signature, we might continue to support calling it both ways for a few weeks until we’d gotten a chance to update all the callers. If we’d extracted a class, we might keep delegating to it, rather than using it in isolation. This let us get important changes in even if we didn’t have time to “push them through all the way”, and also gave us a chance to try out the new design while it was still easy to migrate back.
Split Files at the End
This is a small technique, but I’ve found it a big help. These days, if I’m splitting out new classes or modules, I’ll almost always start by doing all the work in the same file I’m extracting them from. Then at the end, when I’m sure they’re ready to go, I’ll move them to their own file. Even if you plan to follow a standard of one-class-per-file, it helps a lot to have everything in one place when you’re first splitting up a class.
Use Tools When You Can
Even without great IDE support for refactoring, we still had some, and we used it whenever we could. Smaller, easier-to-implement refactorings like Rename or Move are often steps in bigger refactorings like Extract Class. Even where the IDE couldn’t refactor for us, it could help us find code that needed to be changed. If you have tools that can give you insight into your code, use them!
And remember: languages with poorer refactoring tools often have richer, more expressive syntax. This makes it harder for an IDE to refactor your code automatically, but means that you can make more meaningful changes in fewer lines of code.