[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
About 'Refactoring for Greater Understanding' I generally think that when a seasoned developer has trouble to understand the domain from the code is a real bad smell.
ReplyDeleteThe strategy of placing a (developer) domain expert pairing with the new guy generally will be great. In fact, we need to introduce new members on the team to check if what we are writing makes any sense at all. Long projects tend to create their own version of the business domain and it may be hard for insiders to check how far they are from the domain they're supposed to be dealing with.
Cheers
Phillip Calçado
http://fragmental.tw
Thanks for the long answer, Jay!
ReplyDeleteWondering if you'd like to give a stab at another question of mine: What, in your opinion, constitutes a "refactoring" and what does not?
In my team, and in general, I've noticed people use the term "refactoring" very loosely, to mean almost any kind of "code change". The danger with this is that people, especially non-developers, may start treating most code changes as trivial (affecting estimates and expectations) as "refactoring" in itself gives a sense of being a small task.
Martin has talked about Semantic Diffusion and Refactoring Malapropism. However, I'm still confused if most code change can be seen as a refactoring if we vary the level of granularity it applies to.
Aman,
ReplyDeleteI'd start with this: http://martinfowler.com/bliki/refactoring.html
And if you still have questions, I'd hop on the refactoring mailing list, I expect they can do a better job than I can.
Here's a short answer for you though:
In general I like Martin's definition, but I think it might be missing one key element: Refactoring does not involve adding new any new features. Refactoring changes the structure or the implementation of existing code and hopefully leads to removing code, but it shouldn't (IMO) add new features. If you are working on a task that is adding functionality, I don't believe you are refactoring.
Cheers, Jay
Good thoughts. What I struggle with at times is encountering the same piece of code over and over again and having it bother me every time I see it. Functionally it may be fine, but the way it's implemented provokes a mental hiccup. Eventually, I just give in and refactor it away. Usually it's a small change. Balancing the size of the change with the impact of the hiccup and making a final decision about what to do can be difficult.
ReplyDeleteThanks for a thought-provoking post.
Matthew
http://matthewbass.com
This comment has been removed by the author.
ReplyDeleteYour comments on the benefit of 'Refactoring for Greater Understanding' made me think of something that Michael C. Feathers called 'scratch refactoring' (where you throw the changes away) in his book Working Effectively with Legacy Code (he describes it here). I've found that I'd been doing a lot of scratch refactoring and often leaving with just a better knowledge of the code I was working on rather than having actually made any significant changes to it.
ReplyDeleteI think it's something that developers can really benefit from, purely as an aid to understanding.
Ben