A Case Against Dependencies

When you need to add a major bit of functionality to your system, what’s the first thing you do? Naturally, you start looking through whatever package repository is appropriate for a solution that fits. While there’s nothing inherently wrong with this, it’s important to think of the trade-offs made by adding external dependencies.

Why Do We Use Dependencies?

Reasons for including a dependency may include one or more of the following:

The problem has already been solved.

It would take a lot longer for me to implement this thing on my own.

It provides some utilities that my language or framework lack.

I don’t know how to do this.

It’s really cool./It’s the new hotness./Everyone is using it.

Some of these reasons are more valid than others. It’s easy to focus on all of the great things that some dependency may provide, but they can quickly be outweighed by the downsides.

Problems with Dependencies

Dependency Hell – Dependencies have their own dependencies, and inevitably there will be some version conflicts. This problem is so rampant that it’s among the first things you become familiar with as a new developer.

“One size fits all” solutions – Open-source software often begins with one person solving a particular problem. But over time, it accumulates features to support other particular cases, so you end up bending or breaking your own architecture in order to fit into some general solution.

Hidden costs – The explosion of package repositories has made it really easy to just npm install (or whatever) that shiny looking package. But even if you’ve done your due-diligence–you see that the package has had commits relatively recently, the list of bugs is not too long, and a decent number of other projects already use it–that dependency comes with a cost. It’s analogous to skipping writing automated tests: It may seem like you’re saving time at the moment, but you’re really just snowballing headaches for yourself down the road.

Maintenance costs – Dependencies require regular maintenance. You think you’ll be able to keep up with updates, but you won’t. Eventually, the whole process will get harder. Fixing a bug in a third-party package is usually harder than fixing a bug in your own code, especially if you have to introduce work-arounds while waiting for your patch to be integrated upstream (assuming it ever does).

Abandonment – The maintainer may decide that the package your project depends on is no longer worth maintaining. If your code is highly dependent on that package, this may force you to continue using outdated and unsupported code (or force you into maintaining it yourself!).

Mitigation

My best strategy for avoiding the issues listed above is to not add third-party dependencies in the first place. Of course, I realize that it’s not prudent to write everything from scratch, and some dependencies are just necessary.

When third-party dependencies must be used, avoid tight coupling. Do whatever is necessary to isolate your business logic from the dependency, making it easier to replace. This is especially important when the dependency is a whole framework, since it’s easy to get swept up in whatever they provide. When it’s impossible even to avoid tight coupling, adding that dependency is just a calculated risk.