Code is read more often than it is written: a single line is written once, but re-read on every nearby edit — by yourself and by colleagues, six months later, without context. Clean Code is a set of habits from Robert Martin about writing so that this second reader understands the code without an archaeological dig. It's not about beauty for its own sake — it's about the cost of change: the clearer the code, the cheaper it is to modify.
Let's go through the main habits — not as dogma, but as answers to the pain "I can't tell what's going on here."
Every chunk that needed a header comment moves into its own function under that same name. handleOrder itself stays a table of contents of four calls — you read it top to bottom without falling into the details.
Names you don't have to decode
A name is the most frequent comment in code. A good name conveys meaning with no explanation:
// before — what is d? in days? in what?
int d = (now - created) / 86400;
// after — the name answers the question by itself
int daysSinceRegistration = (now - created) / SECONDS_IN_DAY;
A few rules that pay off immediately:
- A name reveals intent.
list→activeUsers,flag→isEmailConfirmed,data→orderPayload. - No cryptic abbreviations.
calcTot()saves four letters and costs a second on every read;calculateTotal()doesn't. - Length matches scope. A counter in a three-line loop can be
i; a class field that lives across the whole module cannot. - A single vocabulary. If it's
userin one place,customerin another, andclientin a third, the reader has to guess whether they're the same thing. Agree on one term — in DDD that is Ubiquitous Language.
A function does one thing
One rule: one function — one job, at one level of abstraction. A function that fetches the data, computes something, and sends an email is three functions fused into one.
// don't: validation, discount, DB write and email — all in one body
void handleOrder(Order o) { ... }
The same flow step by step: each step is changed and checked on its own.
live example
public class OrderFlow {
record Order(String id, int amount) {}
public static void main(String[] args) {
handleOrder(new Order("ord-042", 1500));
}
static void handleOrder(Order o) {
validate(o);
int total = applyDiscount(o);
save(o, total);
notifyCustomer(o, total);
}
static void validate(Order o) {
if (o.amount() <= 0) throw new IllegalArgumentException("amount must be positive");
}
static int applyDiscount(Order o) {
return o.amount() >= 1000 ? o.amount() - o.amount() / 10 : o.amount();
}
static void save(Order o, int total) {
System.out.println("saved " + o.id() + ", total " + total);
}
static void notifyCustomer(Order o, int total) {
System.out.println("email: order " + o.id() + " for " + total);
}
}
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Free week →
Signs a function needs splitting: it doesn't fit on the screen, it nests several ifs, or you write a header comment like // now calculate the discount — that comment is the name of the function to come.
Flag arguments: send(message, true) — what does true mean? A boolean parameter usually means the function does two different things; two functions are often more honest — sendNow(message) and scheduleSend(message).
Comments: why, not what
A good comment explains what the code can't say — why it's done this way. A bad one restates what the code does and over time starts to lie: the code was fixed, the comment was forgotten.
// bad: restates the code — goes stale on the first edit
// increment the counter by 1
counter++;
// good: explains the non-obvious "why"
// We repeat exactly 3 times: the payment gateway caps at 3 attempts per idempotency key.
retry(3, () -> gateway.charge(token));
The best comment is the one you managed not to write: if you feel the urge to explain a chunk, first extract it into a function with a telling name. Comments are justified for the "why," for warnings about non-obvious consequences, and for a public API. Code commented out "just in case" isn't a comment — it's clutter: git is there for history.
Formatting and structure
Readability is also visual order. Keep related lines together, separate meaningful blocks with a blank line, keep nesting shallow. Unwind deep if inside if with an early return: the cases that don't fit are cut off at the top, and the main logic stays on the left, with no staircase.
live example
public class Guards {
record User(String name, boolean active) {}
public static void main(String[] args) {
User[] users = {new User("Ann", true), new User("Ivan", false), null};
for (User u : users) {
System.out.println(nested(u) + " | " + guarded(u));
}
}
static String nested(User u) {
if (u != null) {
if (u.active()) {
return "greeting " + u.name();
}
}
return "skipping";
}
static String guarded(User u) {
if (u == null) return "skipping";
if (!u.active()) return "skipping";
return "greeting " + u.name();
}
}
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Free week →
Both answer the same way — the output shows it. The difference is how many conditions the reader carries in their head by the time they reach the main line.
A single style across the project is kept by an autoformatter, not by arguments in review.
Duplication and boundaries
DRY (Don't Repeat Yourself): one piece of knowledge lives in one place. Three copy-pasted blocks computing a price are three places where someone will forget to fix the tax tomorrow. But even here, no fanaticism: two outwardly similar chunks that change for different reasons are not duplicates, and merging them is harmful.
Work with the outside world — a database, someone else's API, files — is hidden behind a thin boundary (a repository, a client) so that details don't leak across the codebase: swapping a library or a database then touches one place, not half the project.
When cleanliness turns into dogma
Clean Code is guidance, not a body of law. Any of it can be taken to absurdity:
- Splitting into two-line functions when the logic reads better in one is no longer clarity but hopping around the file.
- "Not a single comment" is exactly as bad as "a comment on every line": the non-obvious "why" must be recorded.
The guideline is simple: clean code is code that, when read, leaves the next person with no extra questions. If a rule helps that, apply it; if reading got harder, the rule lost.
In short
- Code is read more often than it is written; cleanliness is about the cost of future changes, not aesthetics.
- Names reveal intent and need no decoding; a single vocabulary matters more than brevity.
- A function does one thing at one level of abstraction; a boolean flag argument is usually a signal to split it in two.
- A comment explains "why," not restates "what"; the best comment is replaced by an expressive name.
- Early returns instead of deep nesting, a single format lives in the autoformatter; DRY — one piece of knowledge in one place, but without merging what changes for different reasons.
- All of this is guidance, not dogma: a rule that makes the code harder to read has lost.
What to read next
- SOLID: five design principles — how clean functions add up to a clean structure of classes.
- GRASP: who is responsible for what — principles for distributing responsibility among objects.
- DRY, KISS, YAGNI and other principles — where DRY has its limit and why the simple option usually wins.
- GoF patterns — ready-made solutions to typical design problems.