Write Code for the Person Who Has to Change It
By Aldridge Dagos, operations software engineer
Readable code gives the next person a short path to the decision they need to change. That matters more than how compact or clever the work looked on the day it was written.
Six files can stand between a reader and a twelve-line rule. The path moves through a helper, a factory, and another object before anything happens. Nothing in that chain has to be broken. The tests may pass, and each layer may look tidy on its own, yet the reader still has to hold every layer in mind at once.
The better version makes that reader’s job smaller. Its names carry the business meaning, while the logic stays near the effect. An abstraction removes real repetition without hiding the decision that someone came to inspect. The writer does more of the translation once so every later reader does less.
A useful test is to read from the point of use toward the rule that changes the outcome. If the path keeps sending the reader elsewhere to learn ordinary facts, those facts belong closer. That is the same reason I design field software so work can resume after an interruption. A person returning tomorrow should not need today’s memory to continue.
Reading map 01
Readable code keeps the decision close
Direct reader path
- Named call site
- Business decision
Abstraction detour
- 01Point of use
- 02Helper
- 03Factory
- 04Wrapper
- 05Business rule
Comment Preserve why the decision exists.
The future reader arrives without your context
Writing code and reading code happen under different conditions because the writer knows the request, the rejected versions, and the conversation that shaped the final choice. The reader sees only what survived into the repository.
That reader may be a teammate next week. It may be the original author after six months of other work. In either case, cleverness that felt obvious during construction has lost the conversation that made it obvious.
This is why passing tests are necessary but incomplete. A test can prove that the program accepts one input and returns the expected result. It cannot tell the next person where a policy ends or why a direct-looking shortcut was rejected.
Customers depend on this even though they never read the code. A clear change path lowers the chance that a small request disturbs an unrelated rule. It also makes the person changing the system more willing to touch the right place instead of adding a safer-looking patch somewhere nearby.
Unreadable code collects fear. Each unexplained layer raises the price of being wrong, so the next person adds another condition around the outside. The old path remains untouched, and the new path becomes one more fact a later reader must discover.
Good code does not need to be brief. It needs to reveal the decisions at the pace a person can follow them. Sometimes the more readable version uses a few extra lines because those lines name the steps that were previously compressed into one expression.
The author benefits too. Clear code makes review more useful because another person can challenge the real decision instead of spending the conversation reconstructing it. Questions reach the rule sooner, while the context is still fresh enough to improve it.
Names carry part of the design
A name is the first explanation a reader receives. Suppose a line holds the total for a refund in the currency’s smallest unit. Calling it simply the amount makes the reader search for both purpose and scale, while a precise name carries those facts to the next line.
Longer is not automatically clearer. Packing every available fact into one label makes the reader wade through details that the surrounding function may already establish. A useful name says what distinction matters here.
Domain language deserves respect. Replacing a precise business term with a generic software word can make the code look familiar while stripping away the distinction the operation relies on. The reader should learn the domain from the names instead of translating around them.
The same rule applies to functions. A generic label hides both the action and the boundary. A precise one can tell the reader that an invoice is being held for review, while the body shows which condition sent it there.
Readable names also prevent comments from doing work that the program can do directly. A comment that says to check whether an invoice can be released adds little when the decision beneath it already has a clear name. The program has a better place for that thought.
Picture a reviewer reaching the release rule for an invoice. In one version, approval, balance, release, and review are compressed into a single branch. The reviewer has to separate the condition from the outcome before asking whether the condition is even right. In another version, the release decision is named before either outcome occurs. Nothing about the behavior changes, but the conversation does. The reviewer can now challenge the business rule directly instead of first translating the writer’s arrangement.
The clearer version may be longer by a line or two. That is a fair price when the line gives an important decision a visible home. Small expressions do not all need names, and naming every intermediate step can make simple work feel ceremonial. The distinction is whether a name removes a real question for the reader.
This matters most around money or permissions, and any action that cannot be casually reversed. The rule described in integer money that keeps units explicit works partly because the representation tells the reader what kind of value is moving. A correct number with a vague name remains easy to misuse.
Abstraction has a carrying cost
Two copies of the same rule can drift. Someone updates one and forgets the other, while a shared abstraction can give that rule one home and protect every place that depends on it.
That shared home is right when the code represents one concept. Tax calculation and access policy need a clear owner when every caller must receive the same answer. Copying those rules into several files creates several versions of a truth that the business believes is singular.
The mistake is treating similar-looking lines as proof of one concept. Two screens may both filter records today while serving different people and changing for different reasons. Combining them too early can make each future change pass through a shared function full of options.
Indirection has a price because the reader must leave the current file, learn another vocabulary, and decide whether the abstraction even belongs in the path under review. If the helper saves four obvious lines but introduces several modes and a configuration object, the repository may contain less text while demanding more thought.
A useful abstraction feels smaller from the caller’s side. Its name tells the reader which decision it owns, and its inputs make the boundary visible. The reader can trust it for this task without opening the file merely to discover what kind of thing it does.
I keep an abstraction when it gives a real concept a clear home and a single reason to change. I leave modest duplication in place when the resemblance is incidental or the two paths are still developing separately. Repetition can be removed later with evidence about what is actually shared.
Waiting for a stable boundary is different from copying large blocks and hoping they stay aligned. The best abstraction removes a question from the reader. A weak one creates a puzzle about which layer owns the answer.
Comments preserve decisions
Comments are most useful when the code cannot carry the reason by itself. A name can show that a payment remains on hold. It may not show that the hold exists because an outside system can acknowledge a request before every transfer reaches a final state.
That comment should explain the decision and the condition that would make it obsolete. It should not narrate the next operation. A note that merely says a retry count is being increased forces the reader to learn the same fact twice.
Time matters here. A workaround without a reason looks like accidental complexity. A future reader may simplify it and restore the exact failure the original decision prevented. One plain paragraph beside the boundary can preserve more value than a long description of the surrounding mechanics.
Comments also need the same restraint as abstractions. A page of prose can become another place that drifts away from the program. Put the durable reason near the code it governs, then let tests and names carry the behavior.
Readable code can still expect a capable reader to know the domain, but it should not make that person recover the author’s private context. Domains have real terms, and hiding them behind generic language can make the program less honest.
Readable code respects the next person’s attention. That person can inspect one rule and change it with confidence. The path stays clearer for whoever arrives after them.
The next reader should inherit the decision, not the archaeology.
Frequently asked questions
Does readable code have to be slower to write?
No. Clear code may take more thought at first, then save time during review, change, and debugging. The author pays for the translation once instead of charging every future reader.
Should a team enforce one coding style?
A shared style helps people scan unfamiliar files without relearning basic conventions. The useful rules reduce needless variation. Personal taste does not need to become policy when either form remains easy to understand.
Can generated code still be readable?
Yes, but generation does not transfer responsibility. A person still needs to confirm that the names, boundaries, and comments fit the system before the code becomes a permanent part of it.
How can readability improve without a rewrite?
Start with the next needed change. Rename the local facts, move the decision near its effect, and preserve any reason the code cannot express. A series of narrow improvements is safer than replacing a working system for neatness alone.