You can’t spend your way out of technical debt

cutting loose

There is an axiom of personal finance that states “You cannot spend your way out of debt.”  While seemingly trite at first, it holds a truth that becomes apparent in nuance.  It becomes a mantra for those seeking a quick-fix to major financial problems: there is a more fundamental axiom stating “You didn’t get into debt overnight; nor will you get out of it overnight.”  Germane to these concepts is the idea that a behavior change is needed to go from in-debt to debt-free.

There is this old component – let’s say it’s a charting component.  Tied to this charting component is a significant amout of technical debt.  Adding new features is expensive, and there are some bugs.  After some exploration, the team has decided they must code a replacement –  in parallel and without the debt, this time.

Once they begin working on the replacement, they hit an interesting snag: the old charting tool did a lot of stuff.  Bar graphs, pie charts, smiley faces.  They use it all over the application for many, many things.  It’s stated as a requirement that the replacement must – from the start – look, feel and behave like the the old one… just without the bugs (and debt, presumably).  Now it looks like the replacement is going to be incredibly costly – perhaps prohibitively so.  This isn’t a solid way out of technical debt.

This is where it gets tough: the behavior change.  The same old requirements/delivery patterns that got you into debt won’t get you out of it.  It’s the opportunity cost of paying off technical debt: you can’t buy functionality with the velocity you use to pay off debt.  You have to realize that those things we ‘got for free’ in the past weren’t really free, after all.  There’s no such thing as a free feature.

One thing you could do instead is ‘downshift’ to incremental development: you get the highest-priority, core-value functionality ready to ship, then you iterate.  This is agile 101.  Each iteration gives you an opportunity to re-evaluate what you want this thing to do.  Perhaps you don’t want the same old stuff as before.  Maybe you get some innovation out of your technical debt pay-down.

The behavior change comes in forgetting what you already had, and build it right this time by managing the process correctly.  You wouldn’t design it all up-front, then deliver a monolith of a feature in a green-field situation, don’t do it here, either.

Thoughts on C# Generic Constraints

A teammate and I were musing about generic constraints the other day, comparing them to checked exceptions in Java.  So I found it amusing that Jeremy Miller came to the same point with Scott Allen yesterday:

Question of the Day — What’s Worse?generic constraints, or checked exceptions [in java]?

Wouldn’t it be nice if we could “break the chain” of constraints at some point, when the calling code ceases to care?  Since I don’t know of a pattern for that, is there something we can do to minimize the constraint pain?

What about the case where the constraint is there only for the convenience of the implementation; say, being able to call “new T()”?

For example:

T Find<T>(int id) where T : Entity, new()
  var result = new T();
  ...// something to do with the Entity type
  return result;

So now, we have this “new()” constraint all over.  Yuck!  Is “Activator.CreateInstance()” really that hard to type?  I understand that “new T()” reads a lot nicer… but that’s one line of code vs. however many consumers now muddied.

Of course, you might say the “Entity” constraint is just as arbitrary to the calling code (more so as you go up the stack).  However, that constraint helps us do something meaningful, but we can live without the “new(),” which provides no real value.

So considering we’re stuck with the current compiler behavior for now, I’d suggest using constraints more to add value to the interface, and less for shortcuts in the one instance of code.  The downside, in the case of “new()”, is that we won’t know about a missing default constructor until run time, but that should be during the unit tests, which run on every build, right?