We are doing another round of refactoring in my current project. This is one of those biggish refactorings that changes the code structure for the better. We've done this before (as I blogged 235 days ago.) And here's some of our thoughts on the matter of refactoring, especially big refactoring.
According to Rob, as you code by adding new features and fixing bugs, the tension in the code increases. It is wise to do refactorings from time to time to release the tension so that the code base remains pliable. This is very important for the continued success of the code base.
Big refactorings, as opposed to regular refactorings that you do as you code, is rather like garbage collection (of the mark and sweep kind) in that i) you stop everything else in the code base to do it; ii) the code base will be cleaner afterwards so that you can code more efficiently.
Here's another analogy: If the entire product is a quilt and each individual features are the piece parts, then regular refactoring can be thought of as ironing out the pieces before stitching them onto the quilt and big refactoring can be though of as ironing out the while quilt. Both are necessary. One doesn't imply the other. I'm tempted to call them local refactorings and global refactorings.
Of course, all these refactoring is made possible by the unit tests and functional tests that we have written. We are not yet "test first" on this project, but we spend quite a lot of time (almost 1:1) writing tests.
Writing tests is such a natural part of the project that time for writing tests are part of the estimates for each feature: "4 hours to write the code, 4 hours to write the tests." Tests are not skimmed even when we are up against a deadline:
Jonathan: I can finished the code before COB today. Do you want me to check in the code?
Kevin: You want to check in broken code?
Jonathan: They aren't broken.
Kevin: Prove it!
Jonathan: (Lost for words. ...) I can write the tests later.