The triumvirite TDD coding cycle of “Red, Green, Refactor” is plastered all over the web and endlessly repeated by all us TDD zombies. If you’re going to drink at the agile Koolaid fountain, don’t stop with just “Red Bar, Green Bar.” You’ve got to do the third part and refactor as you work to keep the code clean. By itself the “Green Bar” is not some sort of holy talisman that wards off all coding evil. Your code may be passing all of its unit tests today, but can you easily add more code to the solution tomorrow? Is your code becoming difficult to read and understand? Are you spending too much time with your debugger? Is there a class or namespace you avoid changing out of fear?
You will slow down if you let problems accumulate. If you’re using evolutionary or incremental design you probably don’t know exactly where you’re going with your design. You are purposely delaying design decisions until you have more information and knowledge about the design needs. Keeping the code clean and each class cohesive with low coupling will maximize your ability to change later. Applications are often discarded and replaced when they become uneconomical or too risky to alter. Constant vigilence and an adherence to good coding practice can extend the lifecycle of a system by reducing the risk and cost of change.
Use aggressive refactoring to keep the technical debt from building up. Integrate refactoring into your normal working rhythm, don’t let it just build up into day long tasks. Here’s the good news though — a lot of refactoring work is quick and easy, especially if you’ll invest some time in learning the automatic refactorings in tools like ReSharper. Look for opportunities to make quick structural improvements. Here’s a sample checklist of quick refactorings (with the ReSharper shortcut combinations) to make on your code as you work. As you work, constantly scan and analyze the code you’ve just written with something like this little checklist and make small refactorings. At a bare minimum, make a refactoring pass before any check in.
- Does the name of the class still reflect its purpose? If not, rename the class (SHIFT-F6). Now, not later.
- Does the name of the method describe the functionality? If not, Rename Method (SHIFT-F6).
- If any method is too long, Extract Method (CTRL-ALT-M) to break up the method into smaller methods. Be aggressive with this technique. Long methods and classes are evil. If there is any part of a method that has a distinct purpose, pull it out.
- If a method is very complex and has a lot of temporary variables and state, consider pulling the method out into its own class (Replace Method with Method Object).
- Is a class getting too big? Does it have a set of responsibilities, methods, or fields that don’t seem to be related to the rest of the class? Extract Class. The Single Responsibility Principle is one of the most important principles in all of software development. Follow it religiously and your TDD experience will be better.
- From the original CodeComplete book, do only one thing on each line of code. A line of code like this: someClass.SomeMethod(variable1 * variable2, variable3.Configure(variable4)); can be confusing because there are so many different things going on in this one line of code. Use the Introduce Variable refactoring (CTRL-ALT-V) to quickly break up the line of code. Besides understandibility, this will make the code easier to step through in the debugger. I’m struggling a little bit with debugging and tracing some code I’ve inherited right now and this is one of the main culprits.
- Look for an opportunity to replace excessive conditional logic with either State or Strategy patterns. Deeply nested “IF/THEN/ELSE” code blocks are a veritable breeding ground for software bugs. You might Decompose Conditional instead to improve the understandability of the code.
- Are you obeying the Law of Demeter (someClass.childThing.grandChildThing.doSomething())? If not, encapsulate the child member calls. This helps to improve coupling between classes.
- Is a class not following the Tell, Don’t Ask principle? Does a method really seem to belong somewhere else? If not, Move Method to put the functionality in another class to improve cohesion and coupling.
- Note to self: replace Magic Number’s with constants where appropriate. Magic numbers are a serious affliction within the .Net community because of our widespread usage of DataSet’s and DataReader’s. I’m bad about this myself.
The technical debt metaphor is a very apt description. Delaying refactoring is a lot like compounding interest payments on your credit card. The longer you wait, the more it’ll cost in the end. On a WinForms project last year we had an absurdly complex navigation scheme. Moving from one screen to another screen involved a variety of security checks, “dirty” screen checks, and activation logic. We knew we needed to refactor our screen controllers to support the navigation in a generalized way, but the team was under severe schedule pressure to make iterations. Against my better judgement I agreed to put off the refactoring to push through more new stories. To my chagrin, a junior pair worked on a couple of new navigation stories and created even more spaghetti code on top of the existing smelly code. The end result was that what should have been a 4-6 hour refactoring turned into about 20+ hours of work. We eliminated the spaghetti code by implementing a Layer Supertype pattern in the controller classes to generalize the navigation checks (CanLeave(), TryEnter(), Start(), etc.). New screens went faster once we made the refactoring. Needless to say, we missed our iteration and the project bogged down. The moral of the story is to recognize and act on the need to refactor earlier rather than later.
Doing something quick and dirty only gives you a short burst of velocity. You’ll pay for it over the long run through reduced coding velocity. It’s like a wide receiver in (American) football making a diving catch. You can only get away with one dive at the end of the run, then you gotta pick yourself off the ground. Brush your teeth twice a day and see the dentist occasionally, and everything is copacetic. Bypass refactoring work and you’ll either slow down the team as the code gets harder and more risky to change, or perform an expensive root canal restructuring on your application to bring it back to health. The most important thing is to be constantly reevaluating your code and design every single day as you work.
One last rant, don’t ever let a project manager or non-coder get away with telling you that “you can just refactor it later.” That’s a little bit like saying you can rest when you’re dead. Refactoring != “throw it away and do it over.” Don’t ever fall into that trap. PM’s seem to assume we’re just goldplating because they often don’t understand the technical situation and the lost efficiency caused by sloppy coding. I’m no longer saddled with bad project managers, but they’re certainly lurking out there.
Do you have some checks to add to the list, or want to disagree with some of the list? I’d be happy to hear your thoughts.