The cost of (avoiding) dependencies
While I'm taking a break on the minimalist SaaS series I started in September, I want to zoom out and take a brief look at the idea of minimalism in software development. Specifically, at how to interpret this idea. Think about what minimalism could mean in daily life. You might discover that there is more than one way to approach minimalism, and these ways may contradict each other.
Let me illustrate this:
Consider you'd want to go minimalist. At some day, you decide you need over-ear headphones with noise cancelling, to shut out distractions. You can foresee that you'll need them at your workplace as well as at home.
The catch:
- If you buy one pair, you have to carry it to work and back home, every day. Carrying extra baggage around isn't quite the spirit of minimalism.
- To commute without a big bag or a backpack of possessions, you'd have to buy two pairs of headphones. But owning two pairs for the luxury of not having to carry them around is also against the spirit of minimalism.
Would you decide for minimal commute baggage or for minimal possessions?
(Or would you turn to become a maximalist and buy not just two pairs of headphones but also a pair of earbuds to enjoy music while commuting? Gosh, this thought experiment gets out of hand…)
Similar dilemmas occur with every software project that strives for minimalism:
- Minimal package dependencies (remember left-pad and xz!) or minimal development work (“don't re-invent the wheel”)?
- Minimize the tool set (Neovim + shell) or minimize development obstacles through an extensive set of tools (language server, linters, AI assistant…)
- And when choosing a programming language: Should it be as lean and clean as possible (which might result in having to write lengthy code), or equipped with every possible feature a language could have (resulting in terse code that's quick to write but cumbersome to decipher)?
I reckon most decisions will end up somewhere in the middle between two extremes. But where is this “somewhere” exactly? You can't just say “pick the middle ground” and instantly know which dependencies, tools, or language to use or avoid.
Speaking of dependencies, you might have heard the advice, “use the standard library!” It's a frequent answer to questions on which library or (heaven forbid!) framework to use to implement some requirement. This advice is undoubtedly takes a bow towards the standard library, but it's not universally applicable.
As always, it depends.
I recently discovered an older article by pure chance that puts the usefulness of dependencies in relation to the size of a project (or, to be precise, to the amount of effort put into a project). The author, Eli Bendersky, came to the conclusion that “the benefit of dependencies is inversely proportional to the amount of effort spent on a software project.” So, a project with a timeframe of two months between start and shipping can take quite some advantage from using libraries and frameworks, while a long-running project that is developed and refined over years may not get any value from doing so.
My minimalist SaaS project isn't planned as a long-running project; it's rather the opposite: In a few weeks’ time, I want to have a running proof of concept that a functional SaaS platform with user management, email and payment integration neither requires writing tons of code nor adding massive amounts of dependencies.
So should I rethink the dependencies part? Would a quick SaaS project be better off bolting some external packages together and call it a day?
The answer is more nuanced than that. First, we talk about a Go project, and Go has a rich standard library that can save you from having to choose third-party libraries for many purposes. Second, there is another factor that drives a decision for our against an external library: the difficulty of the problem to be solved. For example, you can write a key/value store by yourself, but it wouldn't be wise (with exceptions) to attempt to write a production-ready SQL database from scratch. Or take security: anything related to securing an app exposed to the internet is non-trivial. Remember the saying: Never roll your own crypto. (Unless you are a security expert.) In general, the more effort (or risk) implementing a functionality or a building block of an app takes, the more it makes sense to use an existing library.
“Independence is the freedom to choose that which we will depend upon.”
― Craig D. Lounsbrough
Thanks to the extensive standard library, a minimalist Go project can achieve a sane balance between minimal own coding and minimal dependencies with a simple rule: Write low-complexity code by yourself, utilizing the standard library where you can; leave complicated building blocks to carefully selected external packages.
“A little copying is better than a little dependency”, a Go proverb says. Note the word “little”.