I like software built with care , healthy teams and puzzle pieces that fit
In the popular imagination, the software engineer is a romantic hero who sits down at a glowing screen, cracks their knuckles, and immediately starts typing lines of code at a furious pace. They don't think; they just build.
In the real world, this is a recipe for building a system that runs beautifully, passes its tests, and solves entirely the wrong problem.
Rich Hickey’s talks have long been a guide for developers trying to escape this trap. He is famous for advocating "hammock time"—the practice of stepping away from the keyboard, closing your eyes, and actually thinking about the problem before writing a single line of code.
Hickey’s talk, Design in Practice, is a masterclass in how to formalize this process. It is the practical companion to his philosophical talks, offering a concrete structure for recording design decisions without drowning in corporate bureaucracy. It also serves as a beautiful elaboration on Architecture Decision Records (ADRs), a concept first popularized by Mike Nygard's 2011 post, which I still reference regularly.
Here are my key takeaways and reflections on the talk.
If you ask the average developer why they don't write design documents, they'll tell you they don't have time. They need to ship features.
But design isn't about satisfying auditors or writing massive Word documents that no one reads. Design is a cognitive savings account. The goals of formalizing design are intensely practical:
Hickey emphasizes that design is essentially a Socratic dialogue with yourself. It requires detaching your ego from your ideas. The goal isn't to prove your favorite solution is right; it's to discover the objective truth of the system.
He suggests framing your progress around four fundamental questions:
By continuously asking these questions, you engage in reflective inquiry—being aware of your own cognitive state.
Part of this discipline is linguistic precision. If two developers use the same word to mean different things, design fails. Hickey recommends compiling a glossary early in the process. Force yourself to define your terms. If you can't explain what a term means in two sentences, you don't understand it yet.
To turn these philosophical ideas into practice, Hickey breaks the software design and development process into six distinct, sequential phases:
graph TD
A[1. Describe] --> B[2. Diagnose]
B --> C[3. Delimit]
C --> D[4. Direction]
D --> E[5. Design]
E --> F[6. Dev]
E -.->|Backtrack if needed| C
Write down the situation as you hear it. Focus entirely on describing symptoms, not solutions. How big is the problem? What is the business impact? If a database is slow, don't say "we need Redis." Say "queries on the user table are taking 800ms, delaying checkout."
Accumulate hypotheses. There is never just one reason for a bug or bottleneck. List them all and use the scientific method to test them one by one. Prove your hypotheses with data, not vibes.
If you do steps 1 and 2 correctly, you will discover that you are facing a Hydra of multiple, interconnected problems. You cannot solve them all. Delimiting means drawing a hard boundary around the single problem you are going to solve right now.
List high-level approaches and evaluate their trade-offs. Hickey suggests putting these in a decision matrix: columns are your approaches, and rows are your criteria (e.g., development time, operational cost, legal compliance). This is where bad ideas go to die, cheaply and quickly, before they cost a single line of code.
Once you have chosen a direction, map out the implementation path. Choose your libraries, database schemas, and API contracts. If you discover during this phase that your assumptions were wrong, backtrack. It is infinitely cheaper to rewrite a design document than to refactor a production database.
Actually write the code. If you did phases 1 through 5 correctly, this should be the easiest, most mechanical part of the entire process. You aren't guessing or exploring; you are simply executing a well-defined blueprint.
Writing code is fun. Thinking is hard.
But as software systems grow in complexity, the developers who succeed are not the ones who type the fastest. They are the ones who know how to sit in the hammock, ask the right questions, and write their decisions down.
For more details, check out the video of the talk or read the full transcript.
Published: 2023-08-17
Tagged: talk clojure design richhickey
If you read my first two posts, you might remember my passionate, intellectual defense of technical self-reliance. I argued that dependencies are a trap, that building your own static site generator is a path to programming sovereignty, and that custom EDN-based rendering is the ultimate expression of data-driven design.
And then, I didn't publish a single post for over two years.
There is a delicious, painful irony here. The very system I built to liberate myself from the bloated abstractions of modern web development ended up silencing me. I fell victim to the classic NIH (Not Invented Here) hangover.
So, what happened? In the real world, things got busy. I started a new job. My wife and I welcomed our second child. My personal programming time, once a vast ocean of late-night hacking, shrunk to a few precious puddles of semi-conscious hours.
But the real reason for the silence was friction.
Every time I wanted to write a quick note, I had to open up custom Lisp files, copy-paste DSL templates, configure Aero tags, and manually structure layout vectors. If I wanted to tweak the design, I had to do major surgery on my custom engine's compiler passes.
I had wanted to take my custom DSL in a brand-new direction, but the sheer mental overhead of building and maintaining that tool started to exceed the utility of the blog itself. A blog's primary job is to be a communication tool—a record of thoughts. If the tool makes writing feel like work, you stop writing.
The ultimate developer trap is optimizing the shovel instead of digging the hole.
As a mid-career engineer, much of my value comes from guiding teams away from rabbit holes and engineering pitfalls. Yet, I had guided myself straight into one.
I needed to apply my own professional judgement to my personal projects. I sat down and asked: What are my actual priorities? If the priority is writing, then the engine needs to be as invisible as possible. The benefits of maintaining my own custom engine had run their course. It was time to outsource the plumbing.
I wanted something lightweight, built on a stack I loved, that handles the boring details while letting me write in plain Markdown. Enter quickblog by Michiel Borkent (affectionately known in the Clojure community as @borkdude).
Quickblog is built on Babashka—a fast, native Clojure scripting engine—and standard markdown parsing. It is clean, minimalist, and doesn't require a master's degree in language design to add a bulleted list. Porting my old posts and custom styles over took an afternoon of work, but the return on investment was immediate: the friction was gone.
This shift in priorities has made me reflect on a lesson I've been learning in parenting.
Lately, whenever my family is running late and I start rushing, my five-year-old daughter likes to look up and say, "Daddy, remember the story of the tortoise and the hare? Slow and steady wins!"
She is wiser than she knows. In software, as in parenting, rushing is almost always a trap. Trying to move quickly on an ineffective path (like building a custom layout engine just to write a blog post) is magnitudes slower than choosing an effective path and moving steadily.
I am no longer in the blog generator business. I am back in the writing business. And hopefully, you'll be reading more from me because of it.
Published: 2023-02-01
Tagged: priorities quickblog personal blog
Every developer, at some point in their career, falls into the same seductive trap. You have some string input—maybe a custom config file format, a DSL for layout, or a set of nested parenthetical commands. You need to parse it.
You think: "I don't need a heavy grammar engine. I'll just write a quick regular expression and a split loop. It'll take ten minutes."
And it does. And it works beautifully. You feel like a wizard.
But then, the requirements change. You need to support nested brackets. You need to handle escaped quotes. Suddenly, that "quick regex" turns into a recursive, state-holding, back-tracking monster. You spend your weekends stepping through 300 lines of nested if/else statements, muttering dark oaths under your breath. You are trapped by the sunk-cost fallacy, unwilling to throw away the subsystem you’ve spent weeks debugging.
A hand-written parser is a financial loan shark: it offers incredibly easy terms upfront, followed by ruinous interest rates later.
If we want to build a better parser, we have to follow the classic Rich Hickey advice: let's decomplect it. We must untangle the three distinct duties that a parser implicitly performs:
f, u, n, c, t, i, o, n are recognized collectively as a single keyword token.Historically, computer scientists separated these concerns into specialized tools. In 1975, Mike Lesk and Eric Schmidt (who would later become the CEO of Google) wrote Lex, a lexical analyzer generator. It was typically paired with Yacc ("Yet Another Compiler-Compiler"), a parser generator.
For decades, CompSci students (including myself, fifteen years ago) were forced to learn these tools. It was dry, theoretical, and slightly painful. But the professors had a point: formal grammars are vastly superior to custom procedural parsing code.
Today, we don't have to use Lex and Yacc. We have tools like ANTLR 4 (Another Tool for Language Recognition), which combines lexer and parser definitions into a single, clean grammar file.
Here is a simple ANTLR 4 grammar for the ubiquitous CSV (comma-separated values) format:
csvFile: hdr row+ ;
hdr : row ;
row : field (',' field)* ' '? '\r'? '\n' ;
field : TEXT
| STRING
|
;
TEXT : ~[, "\r\n]+ ;
STRING : '"' ('""'|~'"')* '"' ;
In ANTLR, lexer rules (which define tokens) begin with an uppercase letter (TEXT, STRING), while parser rules (which define structure) begin with a lowercase letter (csvFile, hdr, row, field).
Look at how declarative this is. You don't write loops or search algorithms. You simply describe what a CSV file is. The generator takes this description and outputs highly optimized parsing code in Java, Clojure, Python, or JavaScript.
The trickiest part of designing your own language is ambiguity. An ambiguous language is one where a single string of characters can match more than one parser rule.
Consider this naive grammar for links:
link: '[[' STRING ']]' ;
alias: '[' STRING '](' STRING ')' ;
STRING: [a-zA-Z0-9 ]+ ;
What happens if the parser encounters [[alias](target)]?
Because both rules start with the open bracket [, the parser has to guess which path to follow. Without sufficient lookahead, it may commit to the link rule, proceed to look for ]], fail to find it, and throw a syntax error.
To avoid this, you should design your language syntax to be structurally unambiguous from the start. If that isn't possible, you can resort to lookahead markers or design your parser rules to explicitly allow optional characters to resolve the conflict. For example, using lookahead checks like ANTLR’s semantic predicates to check what characters follow.
If you want to escape the cycle of hand-written parser grief, I highly recommend:
Published: 2022-12-11