On Developer Experience

By Radosław Miernik · Published on · Comment on reddit

Table of contents


Writing concise and maintainable code is an art. It’s not only important from the developers’ perspective but also on a (seemingly) unrelated business level. According to this Stack Overflow’s Pulse Survey, developer experience became the most crucial factor in making a company appealing.

On the one hand, we see an increased amount of tooling for every (no longer necessarily major) technology. Our toolboxes are no longer limited to an IDE1 and a debugger/profiler pair. We have code linters, configuration checkers, secret scanners, complexity analyzers… Tons of bugs are no longer possible2, or at least are easier to find due to the automated checks.

On the other hand, every programming language has its idioms. If you’ve ever worked with Python, you’ve most likely heard the term “pythonic”. There’s no clear definition, so let’s use this: “feels right for an experienced developer”. Other languages have their own versions of it, but somehow this term simply sticks better than the other – “javascriptic” sounds terrible.

So what is missing?

Ease of configuration

If you’re a power user, you’ve most likely configured tens of programs with thousands of options. Whether it’s an IDE, VCS, shell, compiler, mail client, ticket tracker, or even a chat – all of them can be adjusted to suit your needs at least a little bit better. (Not entirely, of course.)

Of course, not all of them are made for technical people. In most cases, we’re limited to an in-app configuration wizard, without a possibility to migrate our choices between machines or users3. Luckily, more and more do have a proper JSON, YAML, or TOML file. Custom formats are fine but often also far worse at reporting syntax errors. (I’m looking at you, NGINX.)

As a Linux user, I do have the ~/.config directory. However, not all programs use it – some prefer polluting the home directory with tons of hidden files and directories. Other do use it, but in a different way – they keep everything there. I get it – keeping all relevant files in a single directory makes sense. But I’d still prefer using ~/.cache for cache. (Electron will get there too, eventually.)

Code is king

Infrastructure as code is one of the best movements that became popular recently (2006…?). The idea is simple – manage the infrastructure (machines, networks, load balancers, etc.) through code. It makes some enormous shell scripts obsolete and frees us from the pain of clicking through hundreds of broken screens4, but also makes setup faster, reliable, and versionable.

The fact that I’m able to recreate the entire production environment of a large application with just a single command (or even five) is just amazing. Having enough RAM and Docker on my laptop, I can have most of it locally as well! I’ve been not that long in the business, but I was setting up a project for three days once. Today, I can set up all of the projects I work with within an hour or two.

Of course, it all comes with a cost. These are new tools that you have to learn, at least the basics. It’s extremely easy to start with Docker, but Kubernetes is a whole new world. Similarly, setting up a single service with autoscaling using Terraform feels like a breeze. But as soon as you have to add a database, load balancer, API gateway, and orchestrate not one but seven services – it’s tough.

I’m not sure what would be the best in this manner, really. Playing with these tools is great, but I somehow feel… Scared? I’ll find $200 to pay for my mistake, but $80k may be too much5. (It can easily kill a dozen startups.)

Curated plugins

The frontend world has been busy creating one “tool” for everyone6 for quite a while now7. What this tool is supposed to do is another story, but hey, it’d be nice to be the one creating the Holy Grail of… Tools?

Looking at the most recent ones, we have Gatsby and Next.js. Under the hood, these are relatively small pieces, gluing a lot of existing things (like Webpack) with their plugins. And luckily for everyone, we’re no longer building such tools ground up – these are made with extensibility in mind. (OK, not all of them.)

So, where is the catch? Well, there are simply way too many plugins. And what is worse, most of them are either no longer maintained or flawed “by design”. The official packages are of excellent quality, but from my experience, there’s nearly always a need to use a community one (or to create one yourself).

Let’s take Gatsby: at the moment of writing, it has 2924 listed plugins8. If you search for “sitemap”, you’ll find 8 packages. The most popular one is official and has more than 535k downloads – that’s great, I’ll use that! But the second one has “advanced” in its name and nearly 80k downloads – is there’s something with the official one then? (The rest have around 1k downloads combined.)

Of course, I have no intention of stopping people from creating new packages, and I don’t want Gatsby to hide them either. But the fact that it’s hard to assess the packages is discouraging for less experienced developers. The rest will manage, but it’d be nice if they wouldn’t have to waste their time on that.

The first time I thought about writing a text like this, I wanted to title it “Your function is not pure and why you shouldn’t care”. Of course, it’s catchy, direct, and “true enough”. But why do people focus so much on pure functions and functional programming9?

Because it helps, simple as that. The introduction of .filter and .map in ES5 changed the ecosystem of JavaScript forever10. And as it impacted so many programmers, the critical mass was reached, and everything shifted towards it. The era of pure functions, immutable data structures, time traveling has begun.

But let’s get back to the title. Let’s take one, very simple function in JavaScript: x => x + 1. Even if you’re not familiar with the language, you know what it does. Is it pure? Not really. If we’ll leverage the fact that + calls the valueOf property if there is one (1, 2, 3, 4, 5), using {valueOf: Math.random} as x will return a different result every time.

Bummer – the function is not pure. What is more, most functions aren’t. Except for the valueOf “trick”, we can do the same with toString, but that’s still simple. The ES6 introduced Proxy – a way to react to certain object operations. (You can think of it as a generalization of getters and setters.) On top of that, we can override the globals via globalThis and its friends. No one is safe; everybody lies. (It’s getting better; they were dying previously.)

But does it matter? Hell no! The x => x + 1 is not pure, but we can treat it as such – in the end, all of the tricks I’ve listed above are not something you see in every day’s code11. And that’s what this section is about – there’s no need to be 100% certain that our function is pure because we should think about the system as a whole and not as a bag of unrelated functions12.

The same rule applies to all trends – purity, immutability, observability… Every single one of them leads to better maintainability of the code – that’s great. Just remember to have limits in it, and think about the gains, not only rules.

Closing thoughts

New Year’s resolutions are not my thing, so I thought I could have a wish list instead. I’ll do my best to make it all happen; at least on a personal scale, so… These are my resolutions after all.

Teaching people about these things is great, especially if you can trick them for a moment and ask how you did that. And valueOf is a good one, trust me.

Also, I came to a conclusion that even if the current state isn’t ideal – I guess it will never be – we, as developers, did a great job. Every year we have better and better tools to do our work. Ah, not only to do it but to actually focus on it.

What will the future bring? I can’t wait!


Consider me “old-school”, but I’m not using an IDE per se. My first and current editor of choice is Sublime Text. Of course, I do use a few ton plugins (44 at the moment; that’s a topic for a separate discussion), including LSP, that integrates the Language Server Protocol into the editor, making it “IDE-like”.


At least they shouldn’t – well-typed programs cannot “go wrong”. Of course, this does not hold in the world of TypeScript or mypy, but these are strictly better than nothing. And remember to use the strict mode!


I hate Slack in this manner. There was a time when I was actively working with twelve workspaces, and there was no option to copy settings between them. Things like notifications schedule or message UI preferences take the longest to set. If you know a way to transfer them – contact me, please!


I’ve used all of the most popular cloud providers out there: AWS, Azure, and GCP. Somehow every single one has a terrible UI. Why can’t I just fill in a JSON template? In the end, the REST API does precisely that.


You can go much higher, but make sure to give Azure some time to expand their data center in south Brazil – then do this.


I’m not sure what other word I should use here. “Framework” is probably the closest one (both Gatsby and Next.js use that), but not all of them fit (e.g., Vite).


Tools like Grunt and Gulp called themselves “task runners” (make is a good analogy). With the introduction of Browserify and Webpack, naming somehow shifted towards “bundlers” – both Parcel and rollup.js use it. What will be next?


The “Gatsby Plugin Library” aggregates all of the npm packages with gatsby and gatsby-plugin keywords that include required files (docs).


I am aware that there are languages where the concept of a pure function is well-defined and can be checked by the compiler. However, here I’m focused on TypeScript as an example of a language without such a strict type system.


See On JavaScript Ecosystem for more, like the introduction of Promises.


The code that depends on such quirks is not necessarily good. There’s a very thin border between smart and too smart. I do the latter way too often.


Don’t cite me on that if you’re trying to justify coupling of entirely unrelated features or pieces of code.