Writing
Read the output, not the source
When a bug has beaten several competent people, the shared assumption is the suspect rather than their competence. In my experience that assumption is almost always that the code says what the system does.
The gap
The source is a statement of intent. It is what somebody meant to happen, expressed in a language that gets transformed several times before anything actually runs.
Between the source and the behaviour there are compilers, bundlers, template merges, middleware, proxies, caches, database planners, and other people’s code doing things their documentation describes imperfectly.
Most of the time that gap is small enough to ignore, which is why the habit of ignoring it forms. And then there is a bug that lives entirely inside the gap, and every hour spent reading the source is an hour spent looking in a place where the answer cannot be.
How I learned it
A site was ranking as one page. Three engineers had each added the correct canonical tag to the route templates. Each verified their own change in their own component. Nothing improved for a year.
I fetched the page and read the HTML the server sent. Two canonical tags, one inherited from a shared layout that nobody had touched in two years and which was not part of any of the three changes.
The bug did not exist in any file. It existed in the merge of two files, neither of which was individually wrong. No amount of reading the source could have found it, and three careful people proved that.
The same shape, in other places
Once you have the habit, you start seeing where it applies, and it is a longer list than I expected.
The query the database actually ran. An ORM emits SQL. What you read is a chain of method calls
and what runs is a statement with joins you did not ask for. EXPLAIN ANALYZE on the real query
answers in seconds what an afternoon of reading the model layer will not.
The request that was actually sent. Not the client code that builds it. The network panel or a proxy log. Headers get added by middleware, bodies get transformed by serialisers, and a parameter you are certain you set turns out to be dropped one layer down.
The bundle that was actually shipped. Not the imports. A tree shaking assumption that did not hold, a dependency pulling in a second copy of a library, a polyfill nobody asked for. The built file is the only honest answer to what you are serving.
The configuration that was actually loaded. Not the file in the repository. Environment variables override, deployment tooling injects, and defaults apply where you thought you had set something. Print the effective configuration at boot.
The permissions that were actually granted. Not the role definitions. The resolved, effective set for a specific user, which is the product of several inheritance rules that individually look simple.
Why it feels wrong to do
There is a real reason this is not the first instinct, and naming it helps.
Reading the source feels like understanding. Reading the output feels like guessing at symptoms, and it carries a slight sense that a real engineer would reason it out from first principles.
That has it backwards. Reading the output is the measurement. Reading the source is the hypothesis. Confirming that your hypothesis matches reality before building on it is not a shortcut, it is the part most debugging skips.
There is also a practical friction: getting at the output is often mildly inconvenient. You need a proxy, or a flag, or to reproduce it somewhere real. That inconvenience is what makes people spend another hour in the editor instead. It is almost always the wrong trade.
The habit, concretely
When something does not behave as the code says it should, my first move is now a version of:
what did the server actually send curl, and read it
what query actually ran log it, or EXPLAIN it
what request actually left the browser network panel
what config is actually loaded print it at boot
what does this user actually have resolve and print the effective set
None of it is sophisticated. All of it is fast. And the number of times it has ended an investigation in under five minutes has changed how I start.
The corollary that matters more
If the output is the truth, then the tests worth having are the ones that read the output.
That is why this site has a script that runs after the build, walks every emitted HTML file, and fails if any page has more than one canonical tag, or a canonical that does not match its own path, or a broken internal link.
It is not testing components. It is testing the artefact that gets served, which is the only thing a visitor and a crawler ever see.
Every one of those checks exists because reading the source would not have caught the corresponding bug. That is the test I now apply when deciding what to verify: could this be wrong in a way that every individual file looks correct? If yes, the check has to read the output.