From "How" to "What": Naming for Readability

The simplest yet most effective strategy to make your code more readable is to name your intent.
Code naturally expresses how something is done — the mechanics, the comparisons, the arithmetic. But readability comes from expressing what is being done — the intent, the meaning, the domain concept.
Giving a good name bridges this gap.
Naming booleans in conditions
Consider this condition:
if (dotIndex == s.length - 1) { ... }
This is a how: how we detect something. But what does it mean? You have to pause, read the surrounding code, and reconstruct the intent in your head.
Now compare:
val endsWithDot = dotIndex == s.length - 1
if (endsWithDot) { ... }
The how is still there, but now it lives behind a name that tells you what is being checked. You no longer need to reverse-engineer the meaning — it's right there.
This matters because:
You'll forget what
dotIndex == s.length - 1means in two weeksYour reviewer shouldn't have to figure it out either
Bugs hide in code you have to mentally decode
Extracting well-named functions
The same principle applies at a larger scale. When a block of code does something meaningful, extracting it into a well-named, well-typed function turns how into what.
// How: the mechanics are front and center
val parts = key.split("\\.")
val prefix = parts.head
val remaining = parts.tail.mkString(".")
val node = nodes.find(_.name == prefix)
node match {
case Some(n) => resolve(n, remaining)
case None => ZIO.fail(new Exception(s"Unknown node: $prefix"))
}
// What: the intent is clear, the mechanics are tucked away
resolveNestedKey(key, nodes)
The how doesn't disappear — it moves into the function body where you can inspect it when needed. But at the call site, you read what is happening, not how.
A good function name acts as documentation that never goes stale. The types reinforce it — the signature tells you what goes in, what comes out, and what can go wrong, without reading a single line of implementation.
The test: can you read it out loud?
When reviewing your code, try reading your conditions and function calls out loud:
if (dotIndex == s.length - 1)— meaningless without contextif (endsWithDot)— immediately clearprocessData(x, y, z)— vague, forces you to look insidevalidateAndNormalizeEmail(rawInput)— self-explanatory
If you can read the code like a sentence, you've moved from how to what.
When to apply this
Not every expression needs a name. if (list.isEmpty) is already a what. The rule of thumb: if understanding the expression requires thinking about mechanics rather than meaning, give it a name.
This isn't about writing more code. It's about shifting the cognitive load from every future reader (including yourself) to the single moment when you write the name.