What is the perfect programming language? The perfect framework? The perfect CI tool or site generator?
For years my friends have been pushing me to adopt Typescript, but I resisted. It’s a great language, don’t get me wrong. I just didn’t think I needed it.
So when I converted a new project from JS to Typescript last week — a complete pause-and-refactor — I figured I might explain why. It’s not because my opinion of Typescript has changed. It’s because Typescript fit the budget and it had the right ROI.
Budgets, investments, and ROIs
I think about software projects through a financial metaphor.
I have budgets: a time budget and a complexity budget. My goal is to deliver within them both, and sometimes those budgets compete with each other.
For example, let’s look at refactoring. A project has gotten overly complex: a couple abstractions didn’t hold up and the code smells. I’m over my complexity budget. I can refactor, but it’s going to take time — so I have to consult my time budget. If I’ve got the time, then I refactor; if not, I pay the debt off in installments.
The developer’s tools
To help manage my budgets, I choose from a number of tools:
- Programming languages
- Test suites
And so on. Tools can be things I build in my project — including systems or toolkits— and it can be pre-existing software.
Using good tools can help me manage my budgets, but good tooling is very contextual. Each tool has a investment cost with an expected ROI (return on investment). If the investment and the ROI don’t minimize my budget spends, the tool isn’t right.
Evaluating ROI against investment cost
The first thing I consider is the expected complexity of a project.
If it’s a small program (perhaps a quick build script) I will use no additional tooling. The expected complexity is so low that no ROI would pay for an investment.
If it’s a medium-sized program, I will use a couple frameworks. I might use ExpressJS instead of Node’s HTTP APIs, or LitElement instead of DOM APIs.
If it’s a large program, I’ll add Typescript, CI tools, and so on. At that point, all of the “best tools” are on the table.
This is the first and most obvious intuition: the more complexity I expect in a project, the more I’m willing to invest in tooling.
Over- and under-investing
The complexity/investment-cost balance means I can “over-invest”: I can make big investments for projects that have small complexity, so the ROI isn’t there. In fact, because tooling can add to complexity, I can sometimes inflate my project’s complexity with bad investments.
I can also “under-invest”: that is, I don’t use enough tooling, and the project complexity inflates as new tasks get layered into the codebase.
Over-investing is common, e.g. using microservices when a simple 3-tier architecture would do fine. Under-investing is also common, e.g. building a Web app with the vanilla DOM APIs when a reactive framework is really needed.
So why do I, an experienced developer, so frequently mis-budget my projects? To understand that, let’s look at upfront vs continuous investment costs.
Upfront vs continuous investments
Investments have two kinds of cost: upfront and continuous.
Upfront cost is the initial task of adding the tool. It’s the yak-shaving and code-rewriting that’s required to use the tool.
Continuous cost is the ongoing tasks of using the tool. It’s writing whatever code or config the tool requires, waiting for builds, maintaining a service, and so on.
The continuous cost for a tool is usually consistent. With Typescript, I’m going to have to write X amounts of types for N lines of code. No surprises.
But upfront costs vary — a lot. Most tools cost much less to adopt at the beginning of a project than half-way through. Upfront costs can become so large that they exceed the value of the project, and if the costs are too large then I’ll either forego the investment or greenfield the project.
Varying upfront costs are often why I make bad investments. I give my best estimate on the expected complexity of a project, I’m wrong, and now I have to pay the price.
How I meet my budgets
Just like there are no perfect tools, there are no perfect answer to budgeting projects. Estimates are hard, requirements change, and sometimes we make bad investments.
And thus: last week I refactored a project from JS to Typescript.
The only good advice I can give is to take an iterative approach when you can— which is another way of saying, put some cushion in your time budget. When I can afford to do that, I use that cushion to continuously evaluate the complexity of my project versus the investments I’ve made. The sooner I realize I’ve mis-budgeted, the sooner I can make a new investment, and thus the the lower its cost will be.
A small aside: Typescript deserves a shout-out for its low upfront costs. There’s a little yak-shaving, but converting JS to TS is pretty simple and starts delivering ROI very quickly. This might be partly why, for example, Typescript wins out while functional languages don’t: by the time you realize you need that kind of help, the upfront cost is way beyond your project’s budget.