[W]hat have your experiences been on deferring refactoring and accruing technical debt?My short answer: As long as you remember that the goal is working software, not beautiful code, I believe you will be able to pragmatically balance time spent refactoring and time spent implementing features.
I believe there are many reasons that developers choose to refactor code. Understanding someone's motivation for refactoring may be helpful in determining if the refactoring is helpful to the project. This entry will focus on why developers choose to refactor and the consequences of refactoring.
Refactoring for Greater Understanding (aka, Refactor to the same thing)
A senior developer once joined a team I was leading, half way through the project. When he joined he saw things that he didn't agree with and suggested that we refactor the code towards a better domain model. Anxious to learn from the senior developer, I paired with him over the next few days while we made various changes to the domain model. Unfortunately, many of the changes that the senior developer suggested could not be implemented due to additional constraints imposed by required features. In the end, the code was refactored to be slightly better; however, the largest benefit was the deep understanding that the senior developer gained from the refactoring. From that point forward he delivered value at the level you would expect from a team member who has been on the project from day one. The project lost 2 development days towards new features; however, it gained a fully productive senior developer only 2 days after joining the project. That developer's contribution in the following months greatly out-weighed the original slow down.
I see Refactoring for Greater Understanding fairly often; however, I don't think it's a bad thing. When developers have a deeper understanding of the codebase they can be more effective at adding to it and suggesting how to improve it.
Refactoring while Implementing New Features
Refactoring while Implementing New Features is where developers need to start thinking in terms of Return On Investment (ROI).
If it takes you 3 days to implement a feature or 1 day to refactor and 1 day to implement the feature, obviously you should refactor and save a day. However, what if it takes you 3 days to implement a feature or 2 days to refactor and 2 days to implement the feature? This is when the context is important to consider. Early in a project you should likely go ahead and do the refactoring. Chances are you will need to touch that same code in the future and you will gain that day of effort back while implementing subsequent features. Conversely, if you are nearing the end of a project and you will not be touching that part of the codebase again until the following release, it may make sense to defer the refactoring until after the upcoming release. Again, this is where difficult questions come in to play: Are we really near a release, or is the release going to be pushed back, thus increasing the likelihood that you will end up adding additional features in the same area?
If there's little difference between the time it takes to implement a feature and the time it takes to refactor and implement, it's almost always the right decision to go ahead and refactor. Accruing technical debt can destroy velocity* in the long term. As a project continues accruing technical debt features will take longer to implement. If the team then decides to address the technical debt velocity will suffer even greater losses. In the end, addressing the technical debt is the proper decision; however, it will likely delay the original project completion date. A likely better path is to be vigilant about addressing technical debt whenever it is pragmatic.
Refactoring to New Ideas
Sometimes a new framework is released or a new technique is found that may replace a portion of your application. Developers are often eager to both remove existing pain points and experiment with new solutions. Refactoring to New Ideas also needs ROI consideration; however, there is often hidden ROI. For example, replacing a section of your code with a framework means there is less code for the existing team and new members to understand. Of course, this must be weighed with the fact that the framework likely isn't bullet proof. However, when using a framework you can not only utilize your team to diagnose problems, you can also utilize the community that uses the framework. Another hidden ROI for utilizing new frameworks or ideas is that you may fail when attempting to put it in your codebase; however, failure is often as important as success. If you never try the framework (or technique) you will never know where it applies and where it doesn't. Today's failure may result in a deeper understanding of the framework that may lead to a great gain in the future when it is utilized in a successful way.
Refactoring for Academic Purposes
Refactoring for Academic Purposes is in direct conflict with delivering working software. In your career you will likely find many lines of code that you do not agree with; however, disagreeing with implementation is not a good enough reason to refactor code. If the code currently hinders your ability to deliver software (or will in the future), you can refactor, but changing code because you philosophically disagree is simply wrong. For example, if you believe that state based testing is the only way to test, that isn't a good enough reason to alter the existing tests that utilize mocks. If those tests become a maintenance problem, that's another issue, but simply disliking mocks does not give you the right to remove them. Creating a beautiful codebase should always be a priority; however, creating working software is the number one priority. To make matters worse, "too much" refactoring generally upsets the business sponsors and project managers. Refactoring is a good thing and everyone should be on board with it. If you can't prove to the business and the project manager that a refactoring is worth doing, you might be Refactoring for Academic Purposes.
* Agile velocity is the rate at which the team has accomplished work in the past, which is about the only thing you can use (except for prayer) to estimate the rate at which they will accomplish work in the future. But it's an estimate only, not a promise. -- JerryWeinberg 2005.06.07