The function that does one thing, named like it does
The hardest bug to find is the one hiding behind a function whose name stopped being true three commits ago.
The hardest bugs I've found weren't broken logic. They were correct logic doing exactly what a function's name no longer described. The name said getUser. The body, six months and four hangers-on later, also wrote a login timestamp, kicked off a welcome email on first sight, and lazily backfilled a missing avatar. Every caller read the name and trusted it. None of them knew that fetching a user could send mail. So when a batch job called getUser in a loop to build a report, three thousand people got welcomed back at 2 a.m.
That bug took a day to find because nobody looks inside a function called getUser. The name is a promise, and we read it instead of the code. A function that does one thing, named like it does, is the cheapest documentation you will ever write — and the only kind that can't drift out of sync with the truth, because the name is the truth.
Names rot in the dark
No function starts dishonest. getUser was honest the day it was written. Then a reasonable person needed to update the last-seen time and the user was right there, already loaded, so why make a second call. Then someone noticed first-time users had no avatar and this was a convenient choke point. Each addition was locally sensible and globally corrosive. The name never got a vote.
This is the quiet tax of the path of least resistance. Adding a side effect to a function that already exists is one diff. Creating touchLastSeen and wiring it into the right call sites is five. The cheaper change wins, and the name slowly stops being true. Three commits later it's a small lie that every reader believes.
The cost doesn't show up in code review, where the diff looks fine in isolation. It shows up months later, in the dark, when someone reuses the function trusting the name — and inherits behavior the name never advertised.
A name is a promise the compiler doesn't check, so the only thing keeping it honest is you.
One thing, at one altitude
"Does one thing" is slippery advice because "one thing" depends on how far back you stand. From the top, "handle the checkout" is one thing. From the bottom, "validate the card number" is one thing. Both are true. The rule that actually holds: a function should do one thing at one level of abstraction, and its name should sit at that level.
getUser should fetch a user. Full stop. If logging in needs to fetch the user, update last-seen, and maybe send a welcome, that's three things — and the function that orchestrates them deserves its own name at the higher altitude, like signIn, calling three honest functions below it.
- →If the name needs "and" to be accurate, it's two functions.
- →If the name has to lie by omission to stay short, it's two functions.
- →If you can't predict the side effects from the name, the name is wrong, the function is too big, or both.
The test I use: read the name, guess what it does, then read the body. Every surprise is a bug waiting for a busy afternoon. A query named like a query should not write. A function named for its happy path should not silently swallow the error case. The gap between expectation and behavior is exactly where the next incident lives.
Make the honest path the easy one
You can't lecture rot away. The reason functions accrete side effects is that accreting is cheaper than separating, and people optimize for the next ten minutes under deadline. So the fix is structural: make the honest decomposition the path of least resistance, not a virtue you summon on a good day.
Two habits do most of the work. First, separate the verbs. Commands change state; queries return data. Keep them apart, and a reader can tell from the name alone whether calling it is safe in a loop. Second, when you feel the pull to bolt a second responsibility onto an existing function, treat that pull as the signal — that's the moment a new named function wants to exist, while the seam is still obvious and cheap.
The orchestration is named at its own altitude; each step keeps its promise.
This costs more keystrokes today. It buys you a codebase you can reason about by reading names instead of bodies — which is the only way reasoning scales past the size one person can hold in their head. The reader who trusts getUser next year is usually you, and you will not remember the 2 a.m. exception you bolted on.
Good naming isn't a style preference or a linter rule. It's the contract that lets people build on your code without reading all of it — and reading all of it is the thing that doesn't scale. Every honest name is a small refusal to make the next person, or the next you, pay interest on a shortcut you took at midnight. Make the function do one thing. Then name it like it does, and keep the name true.
