Using NDepend to Avoid Technical Debt
Editorial Note: I originally wrote this post for the NDepend blog. You can check out the original here, at their site.
The term “technical debt” has become ubiquitous in the programming world. In the most general sense, it reflects the idea that you’re doing something easy in the moment, but that you’re going to pay for, with interest, in the long run. Conceived this way, to avoid technical debt would mean to avoid taking out these “time loans” in general.
There’s a subtle bit of friction, however, when using the (admittedly very helpful) concept of technical debt to communicate with business stakeholders. For them, carrying debt is generally a standard operating procedure and often a tool, and it doesn’t have quite the same connotation. When developers talk about incurring technical debt, it’s overwhelmingly in the context of “we’re doing something ugly and dirty to get this thing shipped, and man are we going to pay for it later.” That’s a far cry from, “I’m going to finance a fleet of trucks so that we can expand our delivery operation regionally,” that an accountant or executive might understand. Taking on technical debt is colloquially more akin to borrowing money from a guy that breaks thumbs.
The reason there’s this slight dissonance between the usages is that technical debt in codebases is a lot more likely to be incurred unwittingly (or improvidently). The reason, in turn, for this could make up the subject of an entire post, but suffice it to say that the developers are often shielded from business decisions and consequences. It is thus harder for them to be party to all factors of such a tradeoff — a role often played by people with titles like “business analyst” or “project manager.”
In light of this, let’s talk about avoiding the “we break thumbs” variety of tech debt, and how NDepend can help. This sort of tech debt takes the form of “things you realize probably aren’t great, but you might not realize how long-term damaging they are.”
For the sake of an easy example, let’s imagine a simple forms over data application. The architect, wishing to allow for some potential future-proofing, decrees that there will be two modules in the codebase: the data access module and the GUI module. Consider three options, from best to worst, in terms of maximizing flexibility.
- The two modules communicate purely through interfaces, allowing either one to be reused and vary independently.
- The GUI module calls directly into the data access module, meaning it’d be easy enough to build a new GUI module on top, but hard to reuse the GUI module with a different database module.
- The GUI module and database module manage to be mutually dependent.
When we’re talking about dependency cycles, we’re talking about option (3). Dependency cycles are insidious because they take two things that are ostensibly independent and betray that appearance. When two units (projects, namespaces, types) are mutually referential, they’re actually a lot closer to just being one giant unit than two separate ones. But their separateness creates the illusion that the application has flexibility… right up until you actually try to cash in on it.
It is for this reason that dependency cycles (mutual reference among 2 or more units) create so much technical debt. It’s often quite easy to introduce a dependency cycle without even realizing it. By the time you discover it, however, getting rid of it might be a nightmare.
NDepend ships, out of the box, with dependency cycle detection capabilities. Avoiding this problem can be as simple as configuring your build to fail as soon as one is introduced.
The use of global state is another technical generator. The initial introduction is so simple and often so tempting. You’ve got class A and class Z who exist at the complete opposite ends of your application’s object graph, and that’s been fine up until this moment. It is at this moment that the marketing department conceives of some new, terrible feature that mandates class Z now talk directly to class Z.
The hard thing to do is concede that your object graph is probably rendered obsolete by this and to start to rewire your application a bit. The easy, technical debt introducing thing to do, is add a public static property to Z and have A and Z talk to one another directly through it. It’ll just be this once, you promise yourself.
But it’s never just the once. That initial global boolean flag will become a global enum, and that will morph into a full blown class with its own properties, methods, delegates, and who knows what else. Your application’s communication logic, like water and electricity, will tend to flow along the path of least resistance. And creating global state is akin to digging a 4,000 foot trench in the Earth between two bodies of water — everything will want to flow through it as your application evolves.
NDepend can help here, too. NDepend ships with a code rule called “avoid the singleton pattern” that makes for an excellent start, since singletons are one of the most frequent vessels for global state. But you can enhance that and go further with CQLinq, creating rules to suit your code and team, such as “warn any time a public static method mutates a field.”
If global state and dependency cycles generate technical debt, we might consider this last concern to be an actual technical debt factory. As software developers, we spend a lot of time with computers and a lot of time making those interactions more efficient. Toward this end, copying and pasting seems, at first blush, like one of the most natural ways to boost efficiency. And, while that may be true for taking the edge off some not-worth-automating data entry, it turns out to be a poor choice for source code.
When you copy and paste source code, you set off a string of undesirable events. In the first place, if there is any bug in that code, you now have multiple copies of that bug, which will create a whole lot more downstream troubleshooting than you saved by not taking the time to create an abstraction. But beyond that, pasting code all over the place creates an outsized dependency on that specific implementation. Over time, an erstwhile desire to improve those clones will be met with, “ugh, we’d have to change things in so many places that it simply isn’t worth it.” So, rather than pursue good solutions, odd work-arounds and further limitations crop up.
One of the perhaps lesser known features of NDepend is its the power tools that ship with it. One of these includes a duplicate code finder. Enlist that to catch this problem earlier, when it’s still manageable.
Take Back Your Tech Debt
If you’re working in a development group that’s insulated from business decisions, it might be hard for you to be a full participant in the discussions about shortcuts and tradeoffs. But that doesn’t mean there’s nothing you can do.
No matter the status of your group, you can get away from the tech debt landmines that you step on — the loans from guys that break thumbs. You can use NDepend to insulate you against a lot of bad debt so that when you do have occasion to temporarily cut corners, it’s a decision you make with eyes wide open.