Refactoring and unit testing seem to have bad stigmas from upper management. From my experience, most managers see no value in performing either of those tasks. Refactoring (rewriting existing code) and writing unit tests take time, which is time that could be used to add valuable features for the customer. While this is true, I would like to touch on some of my personal experiences with refactoring and unit testing that have proven that there is a value to those tasks.
The Golden Book of Refactoring
First I'd like to cover refactoring. Years ago, I read Martin Fowler's excellent book Refactoring: Improving the Design of Existing Code. This is probably the most important book I've read in my programming career. The book covers the process of identifying code to refactor (through the use of "code smells" that are rules of thumb to identify potentially bad code), the different refactoring techniques that can be applied, and how to actually apply the refactoring techniques. While around 50% of the book is obsolete (most modern IDEs provide built-in support for performing the refactoring techniques, which is a large part of the book), the rest of the book is pure gold and still worth the read just to learn how to identify bad code and know which refactoring technique to apply to fix it. After I read this book, I completely changed how I view coding. I found myself seeing potential refactorings before I even wrote the code, and I changed the design before even getting started so the code wouldn't need to be rewritten in the future. As soon as I read it, I started tearing apart our baseline at work. I found all sorts of places that needed cleaning up. Every month, my line of code count was in the negative, which management wasn't too pleased with as that was our main metric at the time (LOCC is a horrible metric).
The Fallacy of "Not Writing New Code is Not Being Productive"
Early on in my refactoring experience, I started getting resistance to changing the code base. It was the standard argument about how I could be working on new features instead of rewriting old ones. So why did I persist with my refactoring arguments? Even more importantly, why did management see the light? Well, the project was a Swing GUI with a lot of screens. Most of those screens were very similar or made of similar components. In the original design when I came on, most of that similarity came from copy and pasted code or someone's independent work to make a similar screen. The average time for a team member to create a screen from scratch was about one week.
Duplicated code is one of those code smells I mentioned earlier. I managed to create a framework of superclasses and extracted classes that formed commonly used components. It took me a little over a week to perform all of the refactoring. In that same time, I could have written one screen and added value to our product! What a waste! Well, it turns out not. After the refactoring was complete, the time to create a new screen using the new base classes and refactored components dropped to one screen per day. So for one screen's worth of effort one week, you got five screens worth of effort the next. I also refactored the styles and sizes of screen components into a set of utilities, so that when all screens used that utility, they would have a uniform look and feel, which was something that was missing before.
That's not to say that the payoff will always be that big. This was an extreme case, but it makes my point about refactoring having value very well.
You Might Break Something!
I recently completed a major refactoring on a set of EJBs for another project. There were about 8-10 EJBs that each consisted of a single method that was between ~200 and ~500 lines long. The code was a nested mess, and once again, it was an example of copy-and-paste happiness. Once again, my new boss was totally against the idea of rewriting the code, mostly because of the possibility of me breaking something. His favorite phrase when I mention refactoring is "Don't break anything." So this time, I added another best practice to the mix: unit testing. For every block of code that I refactored out of these massive EJBs, I created a unit test to ensure that the block worked as expected. Now not only was the code cleaner and better organized, but it was now tested as well.
Testing Takes Too Long and Is More Code to Maintain
I tend to hear this one not just from management but from other team members. On another project separate from the two mentioned above, my team implemented unit testing late in the game. The benefits were still there though. Can you guess the results? We found bugs (which is what unit testing is designed to do), some of which had been in the system for ages. We also tended to break code less, since we ran the unit tests every build to regression test our work. Every time we did discover a bug, we wrote a unit test to expose it and then fixed it. We were lucky on this project that management was supportive of our decision to write unit tests (mostly after we completed another major refactoring effort that improved productivity dramatically).
Unit testing also provides another benefit: improved documentation. If a unit test is well written, it can often uncover gaps in the documentation (what happens if I pass
null for this parameter? what if I use a negative number for this one?). Many times, I ended up rewriting the Javadocs to better reflect what the method was doing after the unit tests exposed its true behavior.
How I've Dealt with Management
So how have I dealt with management pushback? Well, on my first program, I had already started the refactoring without permission. Once our team started reviewing the code and realizing how much was changing, I started to get heat about it. So I explained my vision about what I was trying to accomplish and how the new code base would be easier to extend and create screens. I quoted a bunch of stuff from Martin Fowler's refactoring book about the problems with the code. I had a plan for my refactoring and by explaining it and the benefits, I was able to get buy in from the rest of the team and management. If you plan on making large sweeping changes to code, explain your changes first and explain their benefits (quote Martin if you have to).
The second way I've dealt with it is to do it in minor steps. As I work on a new feature that is similar to and older one, I clean up a bit of the older one and reuse what I can. On the project that added the unit testing, the refactorings were done very incrementally, but over time, the payoff was huge in productivity gains for adding new features. The general idea is leave the code you touch better than when you started with it. Extract out a method or class here and there that can be reused, or rename or document something that is unclear. Add a unit test for both the older feature and the new one. Little changes over time can add up without causing too many ripples.
So in conclusion, while unit testing and refactoring do take up time that could be spent developing features, they also provide benefits by making software more robust and easier to understand, maintain, and extend, which can make adding those new features much easier going forward. From my experience, refactoring often (but not always) leads to productivity gains over time. Feel free to use my examples next time you have to convince your manager that they are good practices and not waste of time.