On code smell and gut feeling
This is an excerpt from the web-book/course Uncluttered: free yourself from Node with import maps and test-driven web development.
If you haven’t noticed by now, the theme running through this entire course is “loose coupling”.
This, as some of you may know, is a term of art in software development – something you see cited in developer arguments and rarely explained.
I didn’t define it for you when I used it earlier because I don’t want you to rely too much on memorisation in your work.
Memory is fallible and degrades surprisingly quickly. Relying on your memory too much in your work leads to errors, both because your recall can be wrong, but also because software changes. What you remember might not be correct any more.
What’s more important is gut feeling. The human mind is great at spotting patterns and deviations from patterns. Instead of trying to remember somebody’s attempt at thoroughly defining loose or tight coupling in software development, I want you to think back to the examples and rule of thumbs.
- Front end code that targets standard browser APIs is loosely coupled to whatever browser is running it at the time. You should be able to swap the browser. If you can’t, then that’s a bug in the browser.
- Shell scripts that target the standard POSIX shell (
#!/bin/sh
) are loosely coupled to whatever shell the end user has running. If a shell’s POSIX compatibility mode isn’t POSIX-compatible, then that’s a bug. The POSIX shell “bang”,#!/bin/sh
, is an indicator of loose coupling. - Tooling built in Node is tightly coupled to Node. Node-compatibility in other runtimes is always going to lag and be less reliable than Node directly.
- Conversely, JavaScript code that targets standard Web Worker APIs is loosely coupled to the runtime and will run in a browser Worker, Deno, and a Cloudflare Worker.
Don’t memorise the words. Look at the code. Treat the code like a food or drink you want to savour. Swish it in your mouth and remember the feeling that nice code gave you.
The tests you write here in this course are tightly coupled with mocha
and chai
and that should give you a slight “icky” feeling. Any change to either library will break your tests. There are no abstractions or layers of indirection to protect you from change. Tight coupling like this is always a risk, but there are ways to mitigate it.
In this case, the proven long term stability of these two libraries works in our favour. Mocha is older than React and its API has changed very little since then – the last major changes would be support for Promises and asynchronous functions. We know from history that these libraries are unlikely to change. This also means that they are dependencies that, ideally, should not change. Ever.
This makes them prime candidates for “vendoring”, where you save a copy of a library directly in your project. This mitigates the coupling risk even further because now they will never change unless you specifically take action to change them.
Tight or loose coupling, the risks that come with too much of either, and what they look like in practice are not something that you can practically analyse when you’re coding. Constantly referring back to definitions or formulas is more error-prone and unreliable than a practised gut feeling.
Get into the habit of asking yourself “does this feel a little bit too tight?” and compare it to practical examples. When you see something that feels flexible and nicely loose without compromising the code, take some time to enjoy the feeling, because that feeling is one of the most practical skills you can develop as a developer.
This applies to any joy you feel when seeing well-made software or nicely done code. That joy is practical. It’s training your gut to recognise when code does or does not work. Become a code connoisseur.
Take the time to smell the code roses as you work.