Before you begin: don’t start this one in a rush. There’s no code here to skim past — just
ideas worth reading slowly and keeping. Come back when you have a quiet moment, and it’ll
repay the attention.
A quick hello
I’m Saki, and I’ve been building for the web a long time — long enough to live through several
“this changes everything” moments and come out the other side. Years ago I wrote a series here
called Writing a Big Application in EXT; ExtJS was the heavy framework of its day. Back in 2013
I also gave a talk in Amsterdam called Writing High Quality Code (recording linked at the end).
I re-watched it recently, and something struck me: almost nothing in it was about ExtJS.
The examples used Ext, but the advice (how to write code another human can live with) had
nothing to do with any framework. It was about habits, not tools. That’s the spark for this
series.
Why a new series instead of updating the old one? Because the world moved on. Today you’re
reaching for React, Vue, Angular, or Svelte, not Ext, and those Ext-specific tricks don’t
transfer. The language moved on too: in 2013 we all wrote untyped JavaScript, where a variable
holding a boolean could be assigned an object the next line, and nothing complained until it
broke in front of a user. These days I’d never start an app that way; I reach for TypeScript
every time.
But the principles underneath all of it haven’t moved. Keeping a large codebase from collapsing
under its own weight is remarkably consistent across frameworks and decades: the vocabulary
changes, the shape of quality doesn’t. So this is the first post in a series I’m calling
Writing a Big Application in Modern Frameworks. Over the coming posts I’ll get specific and
practical, with real code — but I want to start where the old talk started, with a simple
question.
Pinning down a slippery word
What does “quality code” even mean?
Notice I said quality, not good. The difference is the whole point. Good code is fine —
it works, it’s average, it gets the job done; a Toyota is a good car. Quality is the rung
above: a Rolls-Royce is a quality car. That higher bar is what this series is reaching for.
Here’s where English hands us a useful bit of wordplay. The word “quality” wears two hats.
As a description it means excellence: “a quality car,” “quality materials,” a high
standard, a degree of perfection. But “a quality” is also just a characteristic: a
property, an attribute, a trait that something has. (Both are everyday English: “a quality
car” uses the first sense; “patience is a quality I admire” uses the second.)
Both senses are in play here, and that’s the point: the excellence we’re after is just the
sum of nameable qualities you can check for, one by one. So this post really answers a single
question — which qualities does quality code have? Let me name them, the way I did on stage
in 2013.
Does quality even pay?
Let’s be honest up front: writing quality code costs more. More thought, more care, more
time, sometimes more money. So is it worth it? The fastest way to answer is to look at
what happens when you skip it. You’ve probably felt at least one of these:
- You fix one bug and two new ones appear in its place or elsewhere.
- Someone else opens your code and can’t make sense of it, and “someone else” is often
you, six months later. - Adding a small feature feels like defusing a bomb. (I have code from years back I quietly
refuse to touch. Please don’t ask me to add anything to it.) - There’s no documentation that’s actually current, so every change starts with an
archaeology expedition through the files. - It works on your machine and falls over on someone else’s.
If any of that sounds familiar, the quality wasn’t high enough. Quality code is, in large
part, simply the absence of these daily papercuts. That’s the payoff: it’s cheaper to
live with, even though it’s more expensive to write. Now let’s make “quality” concrete.
Naming the qualities
Together they add up to one thing: code that works and keeps working as it grows. Here they are.
1. It’s beautiful and pleasant to read
Let me start with something the trade quietly stopped talking about: code can be beautiful
(or ugly), and you can tell which at a glance, before you’ve read a single line. Beautiful
code has even indentation, room to breathe, a clean silhouette on the screen; ugly code is a
dense, ragged wall. We’re human, and humans are drawn to beautiful things and recoil from ugly
ones. That instinct isn’t a distraction — it’s quietly working for you.
Here’s the chain it sets off. Beautiful code invites you in. Because reading it is a
pleasure, you actually read it (properly, line by line) instead of skimming, sighing, and
guessing. And reading is how understanding happens. Understanding is the real goal: it’s
what we’re truly after every time we open a file. Beauty is simply the doorway that gets us
there; ugliness is the door slammed in our face.
So never lose sight of who the reader is: not the computer — a person. The machine has to
run your code, true, but the real audience is another developer trying to understand it, and
that developer is very often future-you, who has forgotten everything present-you knows. Write
like a novelist, not a puzzle-setter: clear, flowing, easy to follow. No clever twists, no
showing off, no compressing three ideas onto one line to feel smart.
This is also where I’ll say the unpopular thing about speed: buying performance at the cost
of readability is almost always the wrong trade. Modern machines are fast and have memory to
spare, and when speed genuinely matters it rarely lives in how quickly a line runs — it lives in
what users actually wait for: needless re-renders, extra round-trips, payloads you didn’t need
to send. So measure first, fix the real bottleneck, and never trade away the next developer’s
understanding to shave a cycle; that’s the scarce resource, not CPU. None of this is a license
to be sloppy — don’t skip cleanup, ignore memory leaks, or refetch what you could have cached —
it just means don’t twist readable code into a clever knot for a speed-up no one asked for and
no one can measure.
2. It follows standards
“Standards” here means two different things, and both matter.
The first is the language’s own rules, and your tooling’s opinion of them (the linters,
formatters, and type checkers that check your code as you type). Modern editors underline these
problems for you the moment they appear. Pay attention to the warnings, not just the
errors. An error stops the program from running; a warning often stops a human from
understanding it. The yellow squiggle is trying to help you.
The second is your team’s own written style guide. This is the part people skip, and
it’s the part that keeps a team sane. You don’t have to write it all from scratch, though:
your framework already ships its own documentation and conventions, and some frameworks even
publish a full style guide of their own. Treat those as the foundation: follow them, and fold
them into your team’s guide so there’s a single place that records how your code is written.
A lot of what goes in there is mechanical: tabs or spaces and how wide, where the braces go,
how you align and space things, colons, trailing commas. The good news is you barely have to
think about it — a modern editor can enforce every bit of it for you. So make the decisions
once, write them down, and configure your tools to apply them automatically. None of these
choices is “right”; they just have to be consistent, so that every file looks like it was
written by one calm person rather than ten arguing ones, and no one ever argues about brace
placement in a review again.
The one part no tool can decide for you is naming, and it’s the part that matters most.
Nomen omen, the Romans said, “the name is an omen”: a good name foretells what a thing is
before you’ve read another character of it. A method does something, so its name reads like a
verb; a variable holds something, so its name reads like a noun. The name should describe what
the thing is — not who typed it. I once inherited code where a developer named Elena had called
her variables Elena1, Elena2, all the way up to Elena20 — twenty names that tell the next
reader absolutely nothing about what’s inside them. Don’t be the reason someone tells a story
like this about your code.
3. It’s modular
Two ideas live inside this one, and you need both.
Small pieces. Break the app into small units that each do one job — one responsibility
each. When a file grows so large that you can’t find anything in it, that’s the signal to
split it. A bug in one small, well-named piece gets fixed once, in one place, and stays fixed
everywhere. And notice the echo of the first quality: small, single-purpose, well-named pieces
are also exactly what make code beautiful at a glance — the qualities reinforce one another.
But small isn’t enough — the pieces also have to keep their distance. The nightmare
isn’t big files; it’s spaghetti, where everything reaches into everything else. In a
spaghetti codebase you fix one thing and twenty others break, because nothing has a clear
edge. The cure has a name — encapsulation: give each piece a small, deliberate, documented
public API (one front door) and treat it as the only way in. Everything behind it is
private; no one gets to climb through a window. That single published door is exactly the clear
edge a spaghetti codebase never has.
A few rules of thumb make this concrete:
- The part that coordinates things shouldn’t know the private guts of the part that
displays things. - A view should announce what happened in meaningful terms (“the user asked to save”),
not leak raw mechanics like “a button got clicked.” - A view (or component, in today’s frameworks) reports; it never gives orders. It tells the
coordinator what happened and lets
the coordinator decide what to do about it — a worker doesn’t march into the manager’s
office and start barking commands. - If you need to change a view, you do it through its own methods, not by reaching in and
yanking on its insides. - One view should never reach directly into another view.
Those rules are one principle in disguise: data flows down, meaningful events flow
up, and nothing reaches sideways. That principle has a name, unidirectional data flow, and
the division of labor it implies (a coordinator that holds the logic; dumb views that only
render and report) is what later becomes the container/presentational split.
The payoff is wonderful: when you swap a button for a menu item, you touch one file, and the
rest of the app never even notices. This idea — pieces talking only through clean interfaces,
data moving in predictable directions — turns out to be the backbone of how React, Vue,
Angular, and Svelte apps are structured today. We’ll spend real time on it later in the series.
4. It’s bug-free
Bug-free is the ideal, and you’ll never fully reach it — but here’s a habit that gets you
closer: when a piece of code gives you a bad feeling, fix it before a user finds it. You
usually know. That uneasy “this is fragile” sense even has a name, a code smell, and that feeling
is real information, not paranoia.
Act on it early, because a bug only gets more expensive the longer it lives:
- caught the moment you smell it — a minute;
- caught in review — an hour;
- caught in production — a frantic afternoon and an apology;
- caught at 2 a.m. — a phone call and a ruined night.
And the smell does more than point at danger — it tells you exactly where to aim a test,
which is how you turn “I have a bad feeling” into “I have proof it works, and proof it’ll keep
working.” That’s what the ninth quality, tested, is for.
5. It’s finished
“But code is never finished!” — I know. The trick is to reconcile that with versions, and to
use a definition you can actually act on: a thing is finished when there is nothing left to
do — in this version. It’s a question anyone can ask, a manager or you yourself: what’s
left to do here? If the honest answer is “nothing,” it’s done. If it’s “nothing… except I
still need to fix the border color in the dark theme,” or “nothing… except write a test for
it,” then it isn’t — that little except is the whole point.
And “nothing left” includes the rest of this very list: a feature isn’t finished until it’s also
clean, documented, and tested. That’s the bar; write it down where the whole team can see
it, in your project’s plan or its style guide, so “done” means the same thing to everyone.
Versions are how “finished” coexists with “always growing.” The application will grow forever,
but version one can absolutely be finished — and so can every release after it, because each
one draws a fresh done-line. That’s the real job of a version: to mark a deliberate boundary, a
point you can call done, instead of an ever-moving “latest” that always points at the newest
code and is never finished. (How you number them — the familiar
MAJOR.MINOR.PATCH — is a convention worth agreeing on and writing down.)
And TODO comments: a finished version has none left that belong to it. If a TODO marks
work for the version you’re shipping, it isn’t really a marker — it’s unfinished work, so do it
before you call the version done. A TODO only earns its place when it’s about a future
version; label it so that’s unmistakable (TODO(v2): …). Anything bigger than a small reminder
belongs in a tracked issue, not a comment that will be forgotten. And for the TODOs you do
decide to keep in the code, your editor can list them all on demand, so none slip away.
6. It’s clean
Clean code carries no clutter. No dead code commented out “just in case.” No leftover
scratch files. No delete-this-after-testing.ts that somehow shipped to production. Your
version history already remembers the old stuff for you, so you can delete fearlessly, and
the project stays free of little landmines that confuse the next reader.
Here’s a single test you can hold every line up to: does the program need it to run, or a
future reader need it to understand? A line earns its place if the machine needs it to run, or if a future developer needs
it to understand. Everything else is clutter — the blind alleys you wandered down and never
tore out, the unreachable branch no path will ever reach, the debug logging you left switched
on, the function nobody calls anymore. None of it helps the program run, and all of it slows
the reader down. If a line passes neither test, it isn’t being saved — it’s being abandoned in
place. Delete it.
7. It’s documented
The rule is short: document it now. Not later, not tomorrow, not after the weekend. The
moment a function works, write it down right there beside it. “Later” never comes — there’s
always one more urgent thing — and by then you’ll have forgotten the very context that made it
easy. Do it while it’s fresh and it takes seconds.
But document the why, not the obvious what. The first quality already makes the code say
what it does through good names; a comment that merely restates that is noise. Spend your words
on what the code can’t say for itself: the intent, the trade-off, the “we do it this odd way
because of bug X” — and, when it matters for understanding, who calls or uses this and when
(“invoked once at startup,” “the only caller is the nightly import”). That’s the documentation
that saves the next reader an afternoon. And aim your words at the front door: a module’s public
API, that one entrance from the third quality, is what other code leans on, so it’s what most
needs documenting; the private rooms behind it can stay quiet.
Two things make this nearly free in a modern codebase:
- Types are documentation. A precise signature already tells the reader what goes in and
what comes out — and unlike a comment, the compiler won’t let it drift out of date: a comment
can lie, a type can’t. - Doc-comments generate the reference. The comments you write beside the code can be turned
into browsable docs automatically — the old wisdom that “the best documentation is the code
itself,” finally without the tax of hunting through hundreds of files for one signature. A
documentation generator walks your source and emits a searchable site of every signature,
property, and type, each carrying the doc-comment you wrote next to it.
8. It works
Last, and least glamorous: it has to actually work. Not “worked when I tried it once,” not
“should work in theory” — actually works, demonstrably, everywhere it’s supposed to run. That
bar is higher than it sounds: not just on your machine but on everyone else’s, across the
browsers and devices you support. And not just under npm run dev — it has to build and
deploy, and still work as the production bundle, where a dev-only convenience can quietly
vanish. “Works on my laptop” is not the finish line.
In a way, the whole rest of this list is just the discipline that makes this final quality
true. But making it true once is the easy part; keeping it true as the app grows — through
a hundred small changes — takes one more thing.
9. It’s tested — the quality the 2013 talk missed
Reading those eight back, I’m proud of how well they’ve aged — and the one thing absent is
absent only because of when I spoke. In 2013, automated testing on the front end was still
rare, so it never even occurred to me to list it. Today it belongs right up there with the rest,
because it’s what turns “bug-free” and “it works” from hopes into facts you can check on
demand.
Not “I clicked around and it seemed fine,” but a real, automated suite that proves the
important things still work — and runs automatically, so a broken change simply can’t sneak in.
That’s the single biggest update I’d make to the talk, and a later post is devoted entirely
to it.
Where we go from here
That’s the foundation — nine plain-language qualities of code you’ll be glad to own:
- It’s beautiful and pleasant to read — invite the reader in, so they actually read it and understand it.
- It follows standards — the language’s rules, your tools, and a written team style guide; good names above all.
- It’s modular — small pieces, each behind one documented front door; nothing reaches sideways.
- It’s bug-free — act on the bad feeling early; a smell tells you where to aim a test.
- It’s finished — nothing left to do in this version, the rest of this list included.
- It’s clean — no dead code, no scratch files; let history remember the old stuff.
- It’s documented — capture the why, now, while it’s fresh.
- It works — demonstrably, everywhere it must run, built and deployed; not just on your laptop.
- It’s tested — an automated suite that proves it, and keeps proving it.
Notice that not one of them mentioned React, Vue, Angular, Svelte — or Ext, or any other language
or framework. That’s deliberate. These
are the laws that hold no matter which framework you pick — or who, or what, does the typing.
And that last part is the twist that makes them matter more than ever. The “what” is the new
arrival: increasingly we don’t write every line ourselves — we describe what we want, and an AI
writes it. That doesn’t retire this list — it promotes it. When you’re directing a machine instead of typing, these nine qualities
are how you steer: the spec you give, the bar you hold the output to, the difference between
code you understand and code you merely accepted. The job shifts from writing toward judging,
and judging is this list. That doesn’t make hands-on skill obsolete, though: knowing how to
write the code well is exactly what lets you judge it well, and tell a solid answer from a
confident wrong one. If anything, the bar on specification and review has only risen.
So over the coming posts, we’ll turn these nine from a checklist into the everyday moves of real
development — how you actually get them into the code you ship. That’s where the principles stop
being a list and start being practice.
See you there.
References
- Writing High Quality Code — Amsterdam 2013 talk
- Writing a Big Application in EXT, Part 1
- Writing a Big Application in EXT, Part 2
- Writing a Big Application in EXT, Part 3
- The Qualities of Quality Code - 26. June 2026