Writing/The Unbearable Lightness of Coding

Chapter 35 · Draft · 15 min read

Epilogue: The Unbearable Lightness of Coding

The Book I Didn't Write

Back in the introduction, I promised a series. This book was supposed to be the first volume: the craft, a common language, Python top to bottom. The second one, Bauhaus Software Design, was actually the original seed of this project: to go beyond the old design patterns, develop a systematic understanding of complexity, and consider the building blocks of real systems — from data formats to storage, communication, and orchestration — and how to fit them together into architectures, from monoliths to microservices to serverless. It was to end with a design philosophy: that good design is not about fashioning objects, interfaces or brands, but about fashioning humans through these material mediums.

But then LLMs happened. As I said in the preface, I finished this book right around the time they came out, and I've spent the time since working with them, learning the ropes and then re-learning them, as the paradigm kept shifting.

For some context, in about 15 years as a software engineer, I've seen three generations of this technology: machine learning at Google, deep learning at Magic Leap, and now LLMs, from real-time conversational engines to agentic repair of enormous financial automations. The first two changed what software could do; the third changed who writes it.

Today, most of my code is written by agents; I've become less of a programmer, and more of a tech lead, defining the direction, articulating the architecture, reviewing the system, and resolving questions as they come up. So I couldn't write the second volume as I planned; a book about software design, where every page silently assumes a human is writing (and reading) the code, would be outdated on arrival — I'd be writing exactly the kind of book I was complaining about in the introduction, with tedious deliberations about design patterns that are hardly relevant to contemporary practice.

Instead, I decided to write a different book: one that rethinks software design in light of AI, and from first principles. This epilogue is a sketch of why, and of what it will be about.

This doesn't render the present book moot, and I don't want to repeat the preface; but I do want to develop it, because the syntax and the libraries were never the point — it was the method, the one I outlined in the introduction, about recontextualizing and renegotiating rules, asking when, where, why and how a best practice came to be. And as it turns out, this method is exactly the tool we need to address the situation: a lot of best practices are suddenly up for renegotiation.

First, I'll address the hidden assumption behind everything I've written so far; then, a few reversals that follow from it; the new problems faced by a senior developer who doesn't write most of the code anymore; and finally, the title of this book, and what it means now.

...For Humans

Do you remember David Wheeler's 1952 paper on sub-routines, from the Complexity chapter? There, he wrote that "all complexities should, if possible, be buried out of sight", but he also said that the "prime objectives are simplicity of use, correctness of codes and accuracy of description". Out of whose sight? For whose use? Nobody needed to ask.

Over the next seventy years, our mental models, processes, tools and design patterns were all conceived and perfected with this single implicit assumption: that code is written and read by humans. Every best practice implicitly ended with "...for humans"; every "easier to maintain" meant "easier for a person to work on"; a lot of abstractions exist solely to accommodate our limits, like our aversion to repetition, context switching and cognitive load. Nobody ever felt the need to spell it out, because who else would be reading and writing software?

This book was no different. In the Design Patterns chapter, we treated them as a shared vocabulary among people; in Managing Complexity, we were mostly concerned with what fits in a programmer's head; type hints were documentation for readers and IDEs; in the CLIs chapter we cared about short flags, helpful messages and colors; in the GUIs chapter we were only interested in shrinking the gulf of execution for a human; in Workflow we were arguing over linters and coding style, which are nothing if not conventions among people.

In other words, a best practice was never really about the truth behind software; it was a compromise between what's good for the system, and what a person can be expected to sustain. We never really noticed the second term, because it was always the same — it was about the same brains, the same patience, the same late-night debugging sessions. But it's not constant anymore, and we're still passing all these practices on to our coding agents, anyway.

Now there actually is someone else writing code; let's call him Claude. What's interesting about Claude is not that he's a better programmer (in many ways, he isn't); but that he's a different kind of programmer, because his constraints are shaped differently. Take attention: it's limited for us, too, but it's a gradual degradation and it's free; a context window is a hard allocation, and it has to be paid for by the token, which in practice means that models struggle with long contexts well before they fill up. A wall of green dots warming my heart as the test suite runs is just noise for an agent; an exit code of 0 means as much as 400 lines of PASS.

In fact, the popular notion of AI as a fast junior developer is closer to the opposite. A junior struggles to handle drudgery, and craves to write clever abstractions; an agent, on the other hand, can grind through any amount of tedium, but an original concept that only exists in exactly one codebase is useless to it, because it can't match it to anything else and has to relearn it every time it comes across it. In other words, boring, explicit, widely attested code is actually cheaper for it, whereas an elegant local invention is not — which is pretty much the opposite of how we're used to rank code.

And you might say, "well, but it learned to program from our code, written by humans, for humans; so those are the conventions it's familiar with." And you'd be right, of course; already back in 2020, long before any of this, I wrote an essay called We Are the AI, where I argued that AI is not actually intelligent on its own: it can take a sliver of a thought, and amplify it to a scale we can't handle, based on abstractions we provide. That's still true, and it shows: agents are measurably worse at dealing with tangled, poorly named code. Bad design didn't suddenly become cheap; the bill was just moved from man-months to tokens.

But I'd like to argue that it's not really an objection. I'm not saying that design doesn't matter; I'm saying that "well designed" was measured against a human cost model, and swapping this model changes the prices of everything. So what happens if we rethink it?

Reversals

Let's start with DRY, because it's the most fundamental principle of them all. Why shouldn't you repeat yourself? Because when requirements change, you have to remember every place to update, and people forget. But an agent will happily make as many changes as necessary, as long as it knows where to make them. If we think of DRY as a solution to the problem of coherence, and that solution was designed for humans, then we can design different solutions — like, maybe we can make repetition discoverable and verifiable, which is better done with a generator and a verification hook than with yet another layer of abstraction.

What about CLIs, the interface I called underrated? If you look at it again, you'll realize it's actually all about human affordances: the flags are short because typing is slow, their ordering and quoting rules are carefully considered, and the colors and progress bars and padded tables are there because we can see them, and they help us see better.

One day, working on a CLI that had to manipulate some pretty complex objects, I was doing my best to design an interface that I would use fluently: nice short names, carefully ordered flags, the whole nine yards. Then it dawned on me that the person who was actually going to use it — some 95% of the time, anyway — was an agent, which would much rather pass in all the necessary parameters as one big, amorphous blob of plain JSON: no ordering rules, no quoting edge cases, nothing to compose. I would hate having to type in a JSON argument — but then, that preference was the entire constraint my design was trying to address.

How about logging? If you think about it, the reason our log lines are in prose is that a person was going to read them, and the reason they're unstructured is that writing a structure is pretty tedious, so we have an entire industry to parse the structure back out — a structure we had in local variables when we wrote the line. In fact, mainframes did it right decades ago: they had message catalogs, with stable identifiers and defined parameters, and they lost out to free text because nobody wanted to update a catalog every time they needed to write a log line. But then, nobody human did — an agent wouldn't mind.

Similarly, when a team of engineers can't agree on some alternative, it's an art unto itself to reconcile the different opinions and coordinate the different tasks, and a lot of what The Mythical Man-Month and Peopleware are about is exactly that. But when an agent can just implement every alternative and measure them, the argument becomes an experiment: no man-months wasted, no egos bruised.

And some things do hold, like clear boundaries, honest naming and low coupling: for the same reason as always — it's about reducing the complexity you need to consider to make a correct change, which is just as important for a context window as it is for a brain.

Tests, too, hold: but for a different reason. For us, they're a safety net; for an agent, they're the feedback signal that makes unsupervised iteration even possible. So in some sense, a quick and dirty prototype might deserve a full test suite, because starting there might get the agent to a working result faster. Even my skepticism about type hints deserves a second look: a type checker is one more cheap verification engine an agent can run in a loop.

What flips is clever compression: deep inheritance hierarchies, metaprogramming, abstraction past the point of clarity — all of these practices trade explicitness for brevity, and it's exactly explicitness that an agent needs most. It might seem a little sad, since I spent whole chapters on decorators and metaclasses; but then, I still think you should understand them, because understanding is how you read what an agent did. But in a codebase maintained by agents, the clever trick is often the expensive one.

And some things weren't really design decisions to begin with: the colored output, the progress bars, the ergonomic flags — these were interface affordances, and the interface was us. Keep them on the human paths; there's no reason to pay for them where no human is looking.

Now, let's apply the method from the introduction to this very moment: pick a convention that you follow on autopilot, and ask yourself what limit this convention compensates for. If it's a genuine engineering constraint, by all means keep it; and if it's a human limit that still applies, because humans still read that code, by all means keep that, too. But if it's a human limit that no longer applies, it's worth looking again: behind it there's often a better design, and it wasn't discarded because it was bad, but because it was inconvenient.

Managing Complexity, with Them

In practice, however, the questions I kept running into as a senior developer were these. How can I review in a day more code than I'd write in a month? The code isn't amazing, but it isn't bad either: how can I tell when it's almost right, and home in on where it's wrong? How far can I trust it: have it consult me when in doubt, catch it when it over-engineers, have it stop when something is good enough? Why does it work in demos and fail on a legacy codebase? And when I ask it why it did something, it seems to invent a story on the spot, so how can I record its real intent and considerations?

I don't have a quick fix, and I don't think one exists. I see these as symptoms of the same cause from before: our processes, tools and design patterns all assume a human programmer, and we just hand them to our agents as-is. The demo is the easy case: a small, fresh project in the most common patterns there are. A legacy codebase is full of local inventions and decisions that nobody wrote down, and the agent has to reconstruct them on every visit, expensively and lossily.

There's a lot of advice out there on how to manage our agents better. I'd rather ask how to manage complexity with them: admit where they're good enough, or outright superior, and focus on articulating the judgement necessary to get the most out of them.

Let's start with recording intent. Rather than reviewing diffs, we can review decisions: instead of reconstructing what an agent decided from two thousand lines of code, have it keep a ledger of the questions it encountered, its uncertainty, and the answers it chose as it went about its business. If you ask it afterwards, you'll get a story; if you ask it for its ledger, you'll get a record.

Then, there's memory. The standard remedy for this is a long markdown file that the agent reads at startup; but this is a form of prose, a distinctly human affordance: it grows over time, can't really be queried, goes stale silently, and is never verified. The alternative is to treat the project's state as data: tasks, decisions and their rationale, conventions — and give the agent a CLI over it. It can pull the two decisions relevant to its task, write back what it decided for the next session, and a rule written as a check can loudly fail, whereas a rule written in prose is forever a suggestion.

Remember what we said in the Workflow chapter, about how if you're having trouble with something for the third time, you should probably write a script? Well, now the agent can do it for itself: go over its own sessions, notice the manual sequences it had to repeat, or mistakes it had to correct, and start building the tools that it was clearly missing.

Finally, verification. The agent says its work is done, and its tests pass: how confident should you be? In one real case, an impressively high rate of success turned out to hinge on mocks that faked the desired outcome. Independent critics, explicit expectations and evidence from the actual system all help, but they're not a reason to stop looking.

In other words, trust is a design problem, not a feeling. The agent should know when to stop and ask, admitting when it genuinely needs a human (I get a ping every couple of hours, arbitrate a question, and sometimes just soothe its anxieties); and everything else should be checked by something more reliable than its own account.

This is the new book in a nutshell: a vocabulary for reasoning about these problems from first principles, so as to answer — if not the questions outright, then the concerns that underlie them.

The Weight That Is Left

Here's the story of this book's title: I originally wanted to call my university course The Unbearable Lightness of Coding, but the teaching committee didn't like it — they said it would look weird next to other courses like calculus and linear algebra. So I said that when the book about this course comes out, that'll be its title — and here we are.

It's a reference, of course, to Milan Kundera's The Unbearable Lightness of Being. The novel begins with Nietzsche's concept of the eternal return, the heaviest of burdens — and with Parmenides, who divided the world into pairs of opposites, and considered lightness as positive and weight as negative. Kundera asks whether he was right; throughout the book, he plays with the concept of "einmal ist keinmal": something that happens only once, might as well have never happened at all. We live once, and that's it — so our lives weigh nothing, and this lightness is freedom; but it's also unbearable.

And in our context, code was always the lightest material there is: free to copy, easy to rewrite, revert and rerun. This book just kept showing that there's so little that actually stands in the way of an idea and its execution — how a function can be replaced by a decorator, a class can be rewritten at its birth by a metaclass, a command line can be generated from a signature, a page can be re-rendered from its state on every click.

But now, with AI, it's more true than ever: code truly weighs nothing. A thousand lines cost minutes — you can have five implementations written out, measure them and throw four away. Einmal ist keinmal, inverted: code happens a thousand times, and none of these times matter, none of them has any weight of its own.

This is the unbearable part: if code is so cheap, what actually matters? In the Workflow chapter, we were worried about tools that let us stop caring — like the C++ programmers who hit F5 and didn't know what a compiler is; in the introduction, we were worried about collage-work, pasting together snippets from StackOverflow. In a previous draft of my metaprogramming book, I wrote that if we won't have junior engineers, we won't have senior ones — because senior engineers don't grow from the earth, and don't sprout up by stitching together ready-made patches; I still believe it, and I don't think the industry has an answer yet. In the same draft, I wrote about how AI won't replace programmers, because the main challenge in making good software isn't writing code, anyway — it's articulating the right problems, coming up with coherent abstractions and designing good interfaces, all of which are inherently social; I didn't expect to stop writing most of my code myself, but that part still held true. The weight didn't disappear — it just moved: care, taste, judgement, design. That's what's left for us — and the craft described in this book, the way of reasoning about problems and their solutions, is how you get it.

Which is why the Bauhaus still feels like the right inspiration. It, too, was born out of an anxiety about art losing its social relevance, and the soullessness of mass manufacturing — and it tried to reunite fine art and functional design, to make objects that are tools, but have the soul of a work of art. Its 1923 exhibition was titled Art and Technology: a New Unity — and it didn't reject the machine, but designed for it.

And the philosophy I promised still stands: good design is about using material mediums to fashion humans. Our new programmers are not humans, but the people using the software — and the people making it — are still human, and fashioning ourselves into engineers that can judge and design, and not just prompt, is the work we have ahead.

Thanks for coming along — through assignments and metaclasses, sockets and event loops, CLIs and GUIs, patterns and complexity. One last time, Saint-Exupéry, as my commander used to say: if you want to build a ship, don't send people to chop wood; teach them to long for the endless sea. The agents chop the wood now — the longing is the part I hope this book has given you.

The next book picks up where this one left off: software design rethought for a world where most of the code is written by agents.

Code weighs nothing now — whatever weight it has from here on is the weight we give it.