Tuesday, July 22, 2008

Paying off Technical Debt Versus Completing Features

I hate technical debt.  Kludgy code, architectural things which looked good then but look like poo now, overly complex or "tricky" sections of a system.  Maybe even low test coverage metrics or shallow tests which get good coverage metrics during stats runs but perhaps don't fully exercise the system.

Technical debt should be paid off as you continue to work on your system, but sometimes, for whatever reasons, we make other choices, skip paying debt down, and let it accumulate as we continue.  This can make a big mess when we hibernate a project for awhile, then attempt to quickly pick things up again at a later point, perhaps even with a set of different folks working on the project.

I just finished up a third phase of a project (third-and-a-half, depending on how you look at it) which has been going in fits and jumps since last April.  The latest phase on the project was to finish up some custom security infrastructure we developed for the client. 

The client had a very small bucket of money for this phase and wasn't going to be able to get all the features she needed completed.  She had enough money for 135 hours of feature development, and her total need was about 210.  Without those 210 she couldn't move on to her global deployment phase, but hours are hours are hours and we don't fudge or cover up the amount of work.  We're completely open with our clients which is awfully important for building long-term relationships.

Well, it turns out that Phil and I kicked butt on the work and completed the 135 hours in amazingly short order.  Now I was at a decision point: pay off some of the technical debt that had accumulated over the entire lifecycle of the project, or push forward with feature development in order to get the system to a point where the client could deploy it?

I spent several commute trips mulling this over (a long commute gives me a lot of time to mull things over...) and decided to press forward with feature hours instead of paying down debt.  It's not a choice I'm overly happy with, but neither option would have left me 100% satisfied.  Why did I make that particular choice in this instance?  Several factors.

First, risk.  My main target for debt paydown would have been the persistence architecture I inherited when I took over the project.  It's overly reliant on static goo and is overly complex.  Testing of that code during refactoring?  Time consuming, even though it's absolutely worthwhile.  The persistence bits work and are fairly sound as written, it's just hard to extend for future use. It makes no sense to do such major rework this late in the game, particularly since I'm all over YAGNI and it looks like there isn't much more extension needed in the near future. 

The next target would have been some WinForms code littered with goo because we did a bad job of enforcing MVPness early on.  I've been slowly refactoring out pieces of that, but it's slow going.  Again, this is a risky section since getting things decoupled and moved out to better separation impacts a lot of different functionality.  Increased regression testing would have been required by the QA folks, and they didn't have a lot of time available.

Decision for these problem areas?  Leave them and move on.

Second, client needs. The client doesn't understand a hooting thing about how our implementation of NHibernate tracks sessions, uses too many static calls, or loads its configuration.  She doesn't know about MVP patterns enabling better testing and separation of concerns, easing testing and extensibility.  The client only knows she has a need to deploy the system in a few months, and she can't do it without the full security stack in place.

Decision for this issue?  Leave the debt in place, enable the client to hit her latest goals -- in part because we all as a team (us + client) had missed some earlier goals. 

The client's now on track to hit her goals, and she's much happier where she's at.  I'm happy that she's going to meet business needs with this latest delivery; after all, we need to deliver value to the customer.  Obviously I'm bittersweet about having missed an opportunity to clean up past technical debt.  At least I can console myself in that I added no new technical debt to this phase of the work. 

Would I do it again? Highly, highly doubtful. This situation was pretty unique, and the victory I got may have been a bit Pyrrhic in that we'll still need to pay off that debt if we get any sort of follow on work.

(By the way, do not confuse me as putting forth features over stable code.  That's not what I've been saying at all.  Technical debt is, to me, an issue different than the stability of what you're writing now.)

(And more by the way, we completed 252 feature hours on her budget of 135 feature hours. Our velocity chart kicks ass!)


Steve said...

Nice summary of a common trade off scenario in software development. It seems like you can always make the old stuff work for "one more release." I wonder if there is a way to identify some tipping point - where the "one more release" strategy will backfire?

Jim Holmes said...

@steve: I'd have been completely against this tradeoff had our velocity been anywhere near projected estimates. I was slightly more comfortable about this decision because we were able to move forward without too much friction because of that debt.

Maybe that's a good tipping point: are you able to move forward without too much friction from debt? If so, you're OK. If not, pay it down.

Subscribe (RSS)

The Leadership Journey