Raul Benencia

The best dependency is the one you don't need

Antoine de Saint-Exupéry, best known for Le Petit Prince, wrote in Terre des hommes that:

Il semble que la perfection soit atteinte non quand il n’y a plus rien à ajouter, mais quand il n’y a plus rien à retrancher.

This is normally translated to English as:

Perfection is finally obtained not when there is no longer anything to add, but when there’s no longer anything to take away.

This philosophy can also be applied to software. The UNIX adage “make each program do one thing well” encourages slim applications that are not bloated with features or dependencies.

In this article I want to focus on dependencies. Every dependency is also a trust relationship: in upstream maintainers, their release process, their response to vulnerability disclosures, and the continued availability of their maintenance time. Dependencies are therefore delegated maintenance and delegated trust. Some are worth that delegation; some are not. The constant stream of supply chain attacks is a reminder that dependencies can be a liability.

Eight years ago I wrote Shoelaces, a tool for automating server bootstrapping. Back in the day, the company I was working for ran entirely on-premises, and we were scaling fast. My role as an SRE was full-stack in the broadest sense: it ranged from fine-tuning complex distributed systems to the mundane work of screwing hundreds of disks into drive caddies before inserting them into servers. We automated everything we could.

Shoelaces was our solution to server bootstrapping automation. Whenever a server was turned on, it would show up on a webpage and allow the operator to choose from a list of recipes. In this way we simplified the installation of bare-metal servers such as Kubernetes workers, Xen virtualizers, and various database servers, among other systems. Shoelaces would also support fully unattended bootstrapping: a CIDR could be associated with a recipe, and every device attempting an iPXE boot on that network would get a full OS installation. This was useful for configuring appliances that would later get shipped to customers.

As my company moved to the cloud, we stopped using Shoelaces in favor of other technologies. Server bootstrapping was replaced with API calls, autoscaling groups, etc. My software fulfilled its task and metaphorically sank with the data center we left behind. However, Shoelaces kept receiving an occasional pull request or bug report, and a small user base started growing. When time allowed, I would spend some cycles applying patches, updating minor dependencies and doing general maintenance.

A task that is slightly more involved is to upgrade dependencies to new major versions. It’s normally not difficult, but it’s repetitive and cumbersome. The advent of LLMs makes these chores more straightforward: a simple prompt can go through a changelog and refactor all code to conform with the new version. In most circumstances, this is good and is the desired outcome. However, simply updating dependencies means that in a few months, or even days, they will be outdated again. Like Sisyphus in the Greek myth, the operator will be condemned to push a boulder up a hill, only for it to roll back down with the next vulnerability disclosure.

I want to point out an alternative to constant patching: prune out unnecessary bloat. Revisit dependencies to understand what is mandatory, and what’s there to make developers’ lives slightly simpler in the short term, at the expense of maintenance toil in the long term. Shoelaces is a small piece of software, and most of its dependencies were used minimally. The backend dependencies included libraries for HTTP routing, middleware chaining, configuration from command-line flags and structured logging. The frontend dependencies included Bootstrap CSS/JS for navbar, grids, and other elements. Bootstrap bundled legacy icon fonts, but there were no first-party references, so they were effectively unused. jQuery was used for AJAX requests, DOM manipulation, event handlers, and fade effects. All in all, these dependencies are nice to have, but they’re not mandatory. I’m neither actively maintaining Shoelaces nor developing new features for it, so the best use of my time is to prepare it for the software afterlife by trimming out anything that’s not compulsory.

I released a new minor version of Shoelaces with that vision in mind. In the backend, most Go dependencies were decommissioned in favor of standard library implementations (the standard library now includes many features that were not present in the previous version of Shoelaces), along with small, focused implementations developed with LLM assistance. I used LLM assistance here in a bounded way: not to introduce new abstractions, but to replace small, well-understood dependency usages with local code that could be reviewed directly. In the frontend, JavaScript and CSS code was replaced with local implementations that now leverage the browser DOM APIs. The interface looks slightly less polished, but the trade-off was well worth making. In this release, the Go dependency set went from 10 module entries to 1, while roughly 800 KB of vendored Bootstrap, jQuery, and font assets were replaced with about 15 KB of local CSS and JavaScript. This simplification also has consequences beyond the Shoelaces tree itself. A few dependencies that had been packaged exclusively for Shoelaces can now be dropped from the Debian archive, reducing not only the application’s dependency graph but also the maintenance surface around it.

Dependencies can be a liability. They might save a few hours of coding in the short term at the expense of Sisyphean patching sessions in the long run. With LLMs, implementing small features normally covered by dependencies is much cheaper. However, I’m not suggesting to reinvent the wheel for every piece of software because that has its own risks as well: more code to maintain and, of course, a larger attack surface. While Antoine de Saint-Exupéry was writing about airplanes when describing his concept of perfection, his words fit the software industry perfectly. Software can gain longevity not through new features, but by reducing the amount of code, process, and external trust it asks future maintainers to carry.