← Back to the section

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."

a header comment is the name of a function asking to come out void handleOrder(o) { // check the order validate(o); // apply the discount total = applyDiscount(o); // write to the database save(o, total); // send the email notifyCustomer(o, total); } validate — rules only applyDiscount — math only save — storage only notifyCustomer — email only

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. listactiveUsers, flagisEmailConfirmed, dataorderPayload.
  • 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 user in one place, customer in another, and client in 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.