Let's get this one out of the way: You have technical debt.
The real question is how much and how bad it is. A little debt isn't enough to worry about, but a lot can make it almost impossible to accomplish anything. So really it's about finding your technical debt, getting a good accounting of it, and knowing what you have to tackle.
So, what increases our likelihood of technical debt, or shows us that we have debt?
Some development methodologies are more prone to technical debt than others. Very heavy very slow methods that place more value in complete and correct than in being on time are going to discourage technical debt. Think NASA: getting a rocket to the moon and back correctly is far more important than doing it in 2009 versus 2010.
XP and other incremental methodologies are particularly prone to technical debt. They don't do design first, so they are more dependent on refactoring during and after development of features - an area that's ripe for technical debt. After all, that refactoring, well, we could ship without it, right? I mean, the feature works. (Technical debt alert!) These methodologies are also highly interested in customer-visible features, and sometimes that tends to leave out the invisible stuff that needs to happen on the back end.
Once you are large enough to have multiple development teams or multiple development areas this factor comes into play. I suspect we've all seen this scenario: the server team adds a reporting element ("% compressed") and forgets to tell the UI team they've done it. So the UI team doesn't add it to the report filter, and it only shows up when you do the "show all" report. Is the feature usable? Mostly, yes. Is it truly done? Well, no. You've gotten a little technical debt here.
Not Addressing Potential Debt Explicitly
When you're defining a feature, or setting out the tasks you will need to do as part of the feature, this is the time to recognize and call out potential technical debt. For example, if the feature is to add a second type of widget, the tasks for adding the second type should include "refactor the first widget type to use the base widget class". The goal is to make more public the work you need to do to avoid technical debt. Make the decisions about accumulating technical debt explicit decisions; don't make them out of ignorance.
Take a look at your defect tracking system. Do you see lots of bugs that say things like, "happens with volume type A but not with volume type B"? How about bugs that are reopened with the comment, "fixed in X but not in Y"? Those are signs of technical debt. Seeing many bugs or large bugs come in after a feature is "development complete" is also a sign of technical debt - complete isn't as complete as maybe it seemed at first.
Generate a class diagram, a state diagram, and if possible a workflow diagram. Do you see neat little boxes or a whole lot of spaghetti? Spaghetti is technical debt, staring you in the face. If you can't generate or draw a class diagram at all, well, that's a really big problem.
Complexity of code isn't inherently bad. However, large and complex dependencies are often indicators that your model is ripe for refactoring, or that you have several half-thought-out ideas going on.
Once you've committed to a release date and a feature set, it can be hard to change. And to change it because you really want to put a button on one more screen? Not likely. The "we have to ship on X because X is the date" mentality is very common (and rightly so - you can't be late forever because you're chasing perfection). However, to meet that date you're likely to cut corners, especially if you've underestimated how much time the feature really takes, or how much other stuff is going on.
More in this series:
- What Is It?: Defining technical debt
- Warning Signs: How do you find your technical debt?
- Paying It Off: How do you reduce your technical debt?