Software Without Compromise

Have you ever worked on a software project and found yourself asking “if only”? “If only I didn’t have to support this legacy codebase”. “If only my customers were clear about what they wanted”. “If only we had more people”. Or “If only I had more time”?

Yes, of course you have. It’s the job. The craft of writing software is fundamentally about handling compromise, understanding trade-offs, and knowing when (or hoping that!) things are just good enough. At least, until the next version! We’ll get it right then, eh?

In my professional career, I’ve known this only too well. I’ve been fortunate to work with brilliant people, but still… we have deadlines, roadmap commitments, rapidly evolving requirements. It’s actually pretty fun, and those challenges go with the territory.

But I’ve always wondered… what would happen if you didn’t have any of those constraints? What if you had all the time you needed? If you could build exactly what you wanted? If you had the time to make your code (well, at least in your eyes) perfect?

No compromises.

Over the last few years, I’ve been tinkering on TinyBase in my spare time. Accountable to no-one, and purely for my own curiosity, it’s a small project that had a chance to explore this possibility. And today I finally launched, which is about as close to this dream of having had “no compromises” as I could personally get.

(I am fully aware that this is in the eye of the beholder! What I think is close to perfect you may think is a hacky prototype. Bear with me, whatever your absolute standards are!)

This post is not about the details of the project itself, but about a few of the things - both good and bad - that I experienced as a result of loosening those constraints.

It’s hard to converge when you can refactor forever

Like all software, TinyBase has grown organically. I didn’t have the perfect vision of what it would be when I originally started, and I started with the basics, and kept layering on new ideas, features, and patterns. I lost count of the times I refactored it - often back and forward between similar ideas.

In a real software project I would have had a finite number of shots at this: breaking API changes are expensive. But when no-one is using your product and you are the only person doing codemods, you can keep moving things around forever. It becomes a curse!

In plenty of cases these oscillations meant that the total distance covered far exceeded the net final displacement - more motion than progress! Actually having a damping function to force convergence would have been far more efficient. Oftentimes I probably needed a peer calling out my indecision.

That said, when I did finally converge on the conventions and patterns I was happy with, I was able to guarantee them consistently across the entire codebase. And that feels good.

Syntactic perfection is cheaper than you think

I feared that polishing the code itself, line by line, character by character, would be where the majority of my time was wasted. I did want the formatting to be perfect!

But here I invested in tooling so I just couldn’t get it wrong - and the result was joyous. Typescript and Prettier did a lot of the hard work of course. I cranked up ESLint for every personal preference I could think of - including how comprehensively every declaration is documented. CSpell found a bunch of naming issues and typos. These are the giants whose shoulders I stood on.

My overall view is that these days, these are things you probably never need to compromise. Once set up, any code - at least JavaScript - can be basically syntactically ‘perfect’ forever, however you choose to define that.

It’s easy to obsess over minification

I wanted TinyBase to be small. It’s in the name. I wanted to have no runtime dependencies so that there was no danger of application bloat.

This caused me to not compromise on supporting legacy browsers. I didn’t want lots of polyfills, for example, and modern terse syntax like nullish coalescing was super appealing. I moved everything that appeared more than once into common libraries - even string literals! - testing every change with the bundler to see if it saved or cost a byte. I even stumbled on things like grouping let and const declarations to save a little more.

In the grand scheme of things, was this effort worth it? I’m happy with the call to keep it modern and free of dependencies. But I spent many hours of my life saving a few tens of zipped bytes for the world, and I’m not sure that was time best spent.

Test coverage: 100.00% or bust

I knew I wanted to try and ship a project that tested every line of code, every branch and every statement. This is definitely not a luxury I’ve enjoyed on any professional projects!

For TinyBase, I wired up Istanbul coverage to Jest and made the coverage report a standard part of my workflow. Sometimes I was able to write tests before code, but more often I just fast-followed with tests after functionality. In the end, it never felt like a drag, and once you get used to the coverage percentage always being in the high 90s (at worse!) it only takes mild motivation to get it back up to 100% again.

Contrast this with having to climb the huge mountain of getting coverage up on a previously un-tested codebase. That’s soul-destroying! I think the trick is to commit to have 100% coverage from as early in the project as possible.

Documentation as a first class citizen

In my experience, documentation is always the last thing to get prioritized on a software project. But having had a previous stint as a technical writer way back in my career I wanted to see what it might mean to put it on more of a pedestal.

For TinyBase, there is rich documentation built into the TypeScript definition files, with a (linted) guarantee that everything has documentation coverage. A toolchain based on TypeDoc turned it into the API documentation. As an extra trick, all examples in the documentation are themselves tested with Jest, which means a guarantee that everything runs, and that there’s a little extra test coverage.

On reflection, I may have overcooked this. It took ages. Even relatively obscure parts of the codebase ended up with multiple working examples and rich explanations. In fact it might even end up being daunting for the reader - making the API seem larger and more complex than it actually is.

But is it valuable?

This is the big one. Untethered by requirements, customer constraints, and deadlines, how do you guarantee that what you are building is actually useful in the real-world?

In other words, it might be ‘perfect’, but is it ‘valuable’?

I do worry about this. Having disappeared down the rabbit hole of creating that utopian project, and then popping up again… what will other people think? It fits how I think of the world, perfectly - but that is almost by definition!

Indeed, I asked a few friends for feedback on the project last week. They were all lovely about it, but one theme that definitely came back was “but why would I use this?”. In creating this thing, had I lost touch with why it should even exist?

Here goes!

Well, we will see. I’m open sourcing it today, and sharing it with the world. I’m a little nervous I guess! But then again, perhaps ‘without compromise’ means that even its reception shouldn’t matter.

At the very least, I now know what it feels like to be able to indulge ones’ self like this.

And an indulgence it was! I think it’s important to finish off with one final point: and that is to acknowledge that working on this project has been a huge privilege in many senses of the word.

I’ve been able to work on a passion project in my spare time for such a long time, complementarily to my professional and family life, and that I can value it for the intellectual stimulation alone. I know how lucky I have been.

And bonne chance, TinyBase!