L. TongWriting
RSS

Reading code like prose

We train endlessly on writing code and almost never on reading it, which is backwards given how the time actually splits.

Hand-drawn figure lying down and reading a book.
Illustration by Pablo Stanley for Open Doodles (CC0).

I have never once been taught to read code. I have been taught to write it in about nine different ways, by textbooks and bootcamps and a decade of reviews, but the reading part was always assumed to come free with the writing.

It does not come free. Reading is a separate skill with its own techniques, and most of us are self-taught in the worst way: by brute force, one confusing file at a time, never noticing that the confusion was a method problem rather than an intelligence problem.

The mistake I made for years

My default approach to an unfamiliar codebase was to start at the entry point and follow the calls. main calls bootstrap, which calls configure, which registers forty things, and by the fourth level of indirection I had a stack in my head that was already full and a mental model that was still empty.

This is depth-first reading, and it is the wrong traversal for the job. It spends all your working memory on control flow, which is the part you can always reconstruct later, and none of it on structure, which is the part that actually takes time to learn.

Read the shapes first

What works better is aggressively breadth-first, and it starts before you open a single source file.

  • The directory tree. Five minutes, no files opened. The folder names tell you what the authors thought the major concepts were, and the ones that surprise you mark where the domain is unusual.
  • The data model. Schema, type definitions, whatever holds the nouns. Code is in service of data, so the data tells you what the code has to do. This is the single highest-leverage file in most repositories.
  • The test names. Not the test bodies. Just the names, read as a list. They are a specification written by someone who had to be precise, and they name the edge cases the authors actually feared.
  • The dependency list. What has been delegated tells you what the team decided was not their problem.

Only then open real code, and even then, start at the boundaries rather than the core. HTTP handlers, CLI entry points, message consumers. Boundaries are where the system tells you what it accepts and promises, in a vocabulary you can check against reality.

Practical version Thirty minutes: ten on the tree and the schema, ten on test names, ten on one boundary file read end to end. You will understand more than three hours of depth-first call chasing gets you.

Read for intent, not correctness

The habit review culture builds is reading to find fault. That is a useful mode and a terrible default, because it makes you evaluate every line against how you would have written it, which is a comparison that generates opinions rather than understanding.

The more useful question on a first pass is: what was this person worried about?

A retry loop with jitter means someone got paged over a thundering herd. A defensive null check on a value that looks like it cannot be null means it was null once, in production, at a bad time. An awkward extra parameter threaded through six functions means a deadline. Every strange thing in a mature codebase is a fossil, and fossils are the actual documentation.

# Retry with jitter. Without the jitter, all 200 workers wake at the same
# moment and take the database down again. Learned this on 2024-03-11.
delay = base * (2 ** attempt) + random.uniform(0, 1)

That comment is worth more than the surrounding module. Someone paid for it.

The reading you should do on purpose

The last piece is that nobody reads code voluntarily, so the skill never compounds. Reading only happens under pressure, when you need to fix something, which is the worst possible condition for learning.

So do some on purpose, with no task attached. Pick a library you depend on and read one file. Not to audit it, not to contribute, just to see how someone else solved a problem in the same language you use daily. Twenty minutes, once a week.

It feels unproductive in the way that stretching feels unproductive. Then a year goes by and unfamiliar code stops being intimidating, which changes what jobs you are willing to take.

If you want a starting point: read the standard library of whatever language you use most. It is written by people who could not make assumptions about their callers, which makes the constraints unusually visible.