| name | readable-code |
| description | Language-agnostic local-clarity craft -- shape control flow, the naming of a whole set, and reading order so a body reads straight down with the smallest working set. Sibling to code-hygiene and code-structure. |
| when_to_use | Writing or reviewing how a function or file READS line by line -- guard-clause control flow, naming for the reader across a set, and the order and locality that shrink what a reader must hold in mind. |
Readable Code
The local-clarity sibling of code-structure. code-structure carves the units and their contracts; this skill shapes how a single body reads to a human once those units exist. Four owners, one test:
- hygiene = DELETE what git or a library already owns -- a restating comment, dead code, a name that lies or says nothing. A rule that says delete X is hygiene's.
- readable-code = READ -- shape control flow, the naming of a set of identifiers, and the order and locality of a body so a reader follows it straight down holding as little in mind as possible.
- code-structure = SHAPE THE UNITS -- decomposition and depth, cohesion, coupling, interfaces, contracts, data invariants. Anything about where a seam falls or how big a unit should be is
code-structure's (it owns the size-vs-depth resolution); this skill assumes the units are already carved and asks only whether each reads clearly.
- pythonica = Python mechanics -- a rule naming a Python construct (a comprehension,
match, a context manager) is pythonica's; keep this skill language-agnostic.
Every rule below is a recognizable smell (something a reader or author can actually spot) -> the principle it violates -> a fix direction. No line or nesting-depth count gates any rule; that false precision is what these qualitative smells replace.
Control-flow shape
The physical shape should mirror the logical shape.
- Buried happy path -- SMELL: you cannot find the main story; it sits at the deepest indent behind a stack of preconditions, tangled at the same level as the error handling, its matching
else far below. PRINCIPLE: the main path should run straight down the trunk at one indentation level while edge and error cases are detected, handled, and dismissed in the margin. FIX: invert each precondition into an early return or continue at the top so the primary path falls through unindented. Judge success by whether the main path became unconditional and reads straight down, not by how many levels you removed.
- Predicate you must invert in your head -- SMELL:
if (!isNotReady), or a two-armed branch whose if handles the rare case while the else quietly holds the norm -- you unwind a double negative or reverse the arms just to learn which case is normal. PRINCIPLE: a positively-named predicate reads as a plain assertion, and leading with the common case tells the normal story first. This is shape, not honesty: a truthful negative flag neither lies nor is empty, so hygiene's tests pass it -- reshaping it for readable phrasing is this skill's job. FIX: name the boolean for its affirmative state (is_ready) and phrase the test positively; where a genuine two-way branch remains, order it so the common case leads.
- Cleverness the next reader must decode -- SMELL: the next reader has to hand-execute the line to know what it yields -- a nested ternary, several operations folded into one dense one-liner, arithmetic on booleans; the author-side tell is a flicker of pride in the compactness, unreliable precisely for the author who prizes it. The cleverness is the point, not a constraint the plain form was measured to fail. PRINCIPLE: code is read far more often than written, so the author's one-time cleverness is repaid as a recurring decoding cost by every later reader. FIX: write the obvious form by default; reach for the dense one only when the plain form is measured to fail a real constraint (a hot path, a memory ceiling) -- and that necessity is exactly the unrecoverable WHY that earns a comment (see hygiene, Comments/KEEP).
- A loop that hides a named operation -- SMELL: a manual accumulation loop reimplements one well-known operation -- a sum, an any/all, a max-by, a group-by -- so a reader must execute the loop mentally to recover an operation that already has a plain name. PRINCIPLE: name the operation, so the code reads as what it computes ("these are the eligible ones") instead of how it accumulates. FIX: replace the loop with the named operation (a map/filter/reduce, a library call) or an intention-named helper. Discriminator (this is the positive complement to the cleverness rule, not its contradiction): this applies only when the loop is one nameable operation; several operations folded into one dense expression stay in the plain form.
Naming for the reader
Hygiene's naming rules judge a single name (does it lie? does it say nothing?). These judge the whole set -- properties no single identifier can violate.
- A drifting lexicon -- SMELL: one operation wears three names (
fetchUser, getAccount, retrieveOrder, all just load from the store) and one domain thing wears three words (user, customer, account used interchangeably) -- no single name lies, yet the reader needs a decoder ring. Grammar drifts too: a value carries a verb-name, an action a noun-name. PRINCIPLE: one word per concept, one concept per word, part of speech matched to role -- a consistent lexicon (the domain's ubiquitous language) lets a reader map name to idea with no translation step. FIX: converge the vocabulary -- pick one verb per operation and one noun per concept, apply it across the module, keep verbs for actions and nouns for values, and bring the strays into line. (This is set-level convergence, not hygiene's single-name rename: no one name here is a liability, the set is inconsistent.)
- Name weight fighting its scope -- SMELL: a two-line loop index named
customerAccountIterationIndex, or a widely-exported value named d. The long name is ceremony the eye wades through in a scope it already holds whole; the terse one goes dark the moment it travels. PRINCIPLE: name length should track scope, not a fixed ideal of descriptiveness -- terse where the whole life of the name is visible, fuller where uses sit far from the declaration. (That an over-descriptive name can itself be the defect is the half hygiene's "says nothing" rule never reaches -- it only ever asks for more.) FIX: shrink ceremonial names in tight scopes; expand terse ones on anything with reach.
- A name that keeps resisting you -- SMELL: the most precise name you can find is still vague --
data, info, state, manager, process -- and not from laziness: every specific name is wrong because the thing genuinely does several unrelated jobs. PRINCIPLE: a name is an abstraction, and a crisp entity yields a crisp name almost for free -- so persistent difficulty naming is a signal about the entity, not your vocabulary. (Inverse of hygiene's "says nothing", where a good name exists and you rename to it; here none exists, so renaming is the wrong move.) FIX: let the failed search drive the design -- narrow or split the entity until one precise name fits.
- An unnamed concept in a fat expression -- SMELL: a conditional carries a compound expression you must simulate --
user.age >= 18 and user.region in allowed and not user.suspended -- while the predicate it computes already has a plain domain name (eligible) that appears nowhere. PRINCIPLE: name an intermediate result whenever the name carries meaning the raw expression does not, so control flow reads in the domain's concepts, not its arithmetic. (Unlike hygiene's meta-rule, this is not about replacing a comment: the name earns its place even where no comment ever was. And a within-body name like this costs no caller an interface -- it is not the shallow-swarm defect that code-structure (Decomposition) warns against.) FIX: extract the subexpression into an explaining variable or named predicate; decompose a fat conditional the same way, naming each clause.
Reader locality, symmetry, and cognitive load
Shrink the working set a reader must carry to understand one line.
- A load-bearing fact stated off-site -- SMELL: to read one line you must source a fact from somewhere it is not written -- a variable whose real type belies its declaration, an ordering or invariant the line depends on but that is stated nowhere near it. It reads as obvious only because you already hold the missing piece. PRINCIPLE: obscurity -- a load-bearing fact anywhere but its point of use -- is a root cause of complexity; if a competent reader finds it non-obvious, it is, and the author's familiarity does not count. FIX: bring the fact to the site that relies on it -- a revealing name or explicit type where the value is used, the invariant stated where it is depended on.
- Too much held live at once -- SMELL: to make sense of one inner line you hold a stack of live context -- two or three still-open enclosing conditions plus a loop variable plus an assignment made far above. Length is not the tell: a short, deeply nested span can cost more than a long flat one. PRINCIPLE: how hard a span is to follow tracks the number of elements a reader must keep live simultaneously, not its line count. (Take the qualitative smell, not a complexity score -- a number's false precision is what this skill refuses.) FIX: reduce what must be co-held -- name an intermediate so one word stands in for a tracked sub-expression, and resolve nested conditions early so each is settled and dropped rather than carried open.
- Scattered pieces of one thought -- SMELL: assembling one thought means scrolling back and forth -- the file states an answer before it poses the question, a name is introduced pages above the first use that gives it meaning so you carry an undefined token live, a callee sits far from its only caller. PRINCIPLE: put what the reader needs together and in the order they need it -- declaration at first use, callee just below caller, general before specific, like a newspaper -- so nothing must be held open across a gap. FIX: move each declaration down to its first use, place callees beneath their callers, order the file summary-to-detail. Two boundaries: this is a reordering, not a removal -- deleting the surplus is hygiene's job; and a single-caller helper that is a genuine, independently nameable unit gets reordered here, whereas a shallow shard that means nothing outside its caller and reaches into its state gets merged away (the shallow-swarm rule in
code-structure). The opposite fixes apply to different defects, not to the same one.
- Parallel work in different shapes -- SMELL: two operations doing parallel work wear different shapes -- one a declarative map/filter, its sibling a hand-rolled loop; one handler returns early, its twin sets a flag and falls through -- and the reader cannot tell whether the difference means anything. At codebase scale: new code adopts a different error style or layout than everything around it for no stated reason. PRINCIPLE: say the same thing the same way, and reserve variation to mark real difference -- so that when code looks different, it means something different. FIX: normalize parallel code to one shape and place symmetric operations (acquire/release) together; match the surrounding convention, or if the old way is wrong, change it everywhere and say so.
- An undifferentiated wall of statements -- SMELL: a body runs validate, compute, format, and emit together with no break, so the reader cannot see where one thought ends and the next begins. Tell-tale patch: a
# ==== compute ==== banner doing the structural work a real seam should do. PRINCIPLE: sequential phases should be visible in the structure before the reader parses any single line. This is the shaping complement to hygiene: hygiene deletes the divider banner, this rule supplies the real seam the banner was faking. FIX: put a genuine seam between phases -- a blank line at least, an extracted named step when a chunk is itself a concept (not merely because the body is long).
- Behavior smeared up an inheritance chain (the yo-yo) -- SMELL: to understand one leaf method you ping-pong up and down a class hierarchy -- a bit of behavior in the leaf, more in a middle base, a template hook two levels up -- so you must hold the whole chain live to know what one call does. PRINCIPLE: behavior spread across an inheritance chain inflates the reader's working set the same way scattered helpers do; a hierarchy earns its keep only when each level is a genuine, independently comprehensible abstraction, not a sliver unreadable without its ancestors. FIX: flatten or collapse levels that exist only to share a little code; prefer composition where inheritance was reused purely for reuse, so one case reads in one place. (Distinct from refused bequest in
code-structure (Data and type modeling), which is contract violation; this is reading cost.)
Scope
What this skill leaves to others, one reason each:
- Deleting anything -- OUT. A restating comment, a dead branch, a name that lies or says nothing is
code-hygiene's; this skill only reshapes code that stays.
- Where a seam falls, how big a unit is -- OUT. Decomposition, module depth, and the size-vs-depth resolution are
code-structure's. A guard clause here flattens nesting; it does not decide what to extract.
- Naming a single liability name -- OUT. A name that lies (
get_* that writes) or says nothing (tmp, data) is hygiene's rename-or-delete; this skill judges the consistency and scope-fit of the whole set.
- Formatting, whitespace, line length, alignment -- OUT. Auto-formatter territory; legislating layout would smuggle in numeric thresholds. A genuine phase seam (a structural break, not a blank-line count) is the rule above.
Sources
Martin, Clean Code (intention-revealing names, one word per concept, the step-down read); Beck, Tidy First? / Implementation Patterns (guard clauses, explaining variables, normalize symmetries, chunk statements); Kernighan and Pike, The Practice of Programming (clarity over cleverness, consistency); Fowler, Refactoring (Replace Nested Conditional with Guard Clauses, Extract Variable, Extract Function); Evans (ubiquitous language).