Skip to main content

Conceptbeginner

Clean Code

Overview

Clean Code is the set of practices for writing readable code — clear names, focused functions, absence of surprise.

The central principle is sound: code is read many more times than it is written, and optimizing for reading pays more than optimizing for writing.

Several of the movement's specific rules, however, produce worse code when followed literally. This document covers both things.

Problem

Readability is treated as aesthetic preference, which prevents productive discussion. "I find this more readable" against "I find that more readable" has no resolution.

The way out is to reframe the question: readability is not a property of the code, it is a relationship between the code and whoever reads it. The operational question becomes how much effort does someone need to understand this well enough to change it safely?

That is observable. One new person reading the code and narrating what they understand reveals more than any debate about style.

Core Concepts

Names

The highest-return item, by a wide margin.

A good name makes the comment unnecessary. A bad name forces the reader to keep a translation in mind throughout the reading.

Three rules that hold up:

  • The name reveals intent, not implementation. delinquentCustomers rather than filteredList.
  • Length follows scope. A loop index can be i; a class field used in twenty places cannot.
  • The vocabulary is the domain's. If the business says "policy", the code says policy. See ubiquitous language.

Functions

A function should operate at a single level of abstraction. Mixing "calculate the amount due" with "format the date for the file's standard" forces the reader to switch levels midway.

The popular rule — "functions should be at most five lines" — does not hold up. Extracting aggressively produces the opposite problem: to understand a flow, the reader jumps through ten three-line functions, and the logic ends up living nowhere.

The useful criterion is not size. It is whether the function tells a coherent story at a single level.

Comments

Clean Code's strong formulation — "a comment is a sign of failure" — is partly wrong.

Comments that explain what the code does are indeed redundancy that ages. But comments that explain why carry information the code cannot carry:

❌ // increment the counter
counter++;

✅ // The provider returns 429 with no Retry-After on this endpoint.
// 800 ms was determined empirically; below that the rejection
// rate starts climbing again.
wait(800);

The second is not a failure. It is the only way to record that.

Surprise is the real cost

Code that does something beyond what its name promises is the most expensive of all, because the reader does not know they need to investigate. A validateOrder function that also writes to the database breaks trust in every other name in the system.

Mental Model

Write for whoever will read it a year from now with no context. That person is frequently you.

When to Use

  • Where reading is frequent and change is expensive — business rules, tests, code other people will maintain.
  • With more rigour where the business logic lives — that is where reading is most frequent and error most expensive.

When Not to Use

As a literal rule about size. Extracting until everything is three lines makes readability worse.

When it conflicts with demonstrably critical performance. On hot paths, less elegant and faster code can be the right choice — with a comment explaining why and the measurement that justified it.

In generated or throwaway code. A one-off migration, an analysis script, a prototype.

As an appeal to authority in review. "That's not clean code" is not a critique until you point at what becomes harder to understand or change.

When the pursuit of purity becomes the work. Refactoring names indefinitely in stable code is cost with no return.

Alternatives

  • Tests as documentation — a well-named test communicates intent better than many comments.
  • Expressive types — a NationalId type communicates more than a well-named String.
  • Design heuristics — more structural criteria, less open to interpretation.

Trade-offs

More extraction and ceremonyLess
Each piece is simpleBigger pieces
Names document the flowFlow explicit and linear
Many jumps to follow the logicLogic in one place
Easy to test in partsCoarser tests

Failure Modes

Excessive fragmentation. Tiny functions always called in sequence, understanding which requires opening all of them.

A name that lies. Describes something other than what the code does. The worst case.

Outdated comment. Describes the code from two years ago and is believed.

Abstraction for readability's sake. Extracting a class to "improve the name" when a well-named variable would do.

Common Mistakes

Treating the rules as literal. Especially the one about function size.

Eliminating all comments. The "why" ones are irreplaceable.

Discussing style instead of reading effort. The question is what becomes harder to understand, not what is more pleasing.

Applying the same rigour everywhere. Domain code deserves more than configuration code.

Real-World Example

A code review asked for a 40-line function to be extracted into eight smaller ones. The author disagreed; the discussion stalled on preference.

The criterion that resolved it: they asked someone who did not know the code to read each version and explain what it did.

On the 40-line version, they took three minutes and got it right. On the extracted version, they took seven and got the order of two steps wrong, because the function names did not indicate sequence.

The final version ended up with three functions, not eight and not one — separating the three levels of abstraction that genuinely existed: fetch the data, apply the rule, persist the result.

What resolved it was not the rule. It was measuring the reading effort of someone with no context.

The rigour varies with the code

Applying the same readability standard across the whole system is waste on one side and negligence on the other.

Kind of codeRigourWhy
Business rulesMaximumRead frequently, changed by many people, error expensive
Infrastructure adapterMediumChanges little once stabilized
Configuration and wiringLowRarely read, changed mechanically
TestsHighExecutable documentation; an unreadable test is not used as reference
Migration, one-off scriptNoneShort life, one consumer
Performance-critical pathSpecialClarity yields to measurement, with a comment justifying it

The test row tends to surprise. Teams invest in production readability and accept tests with duplicated setup and generic names — when the test is precisely what the next person reads to understand the code's intent.

Practical Exercise

Pick a file in your system and ask someone who does not know it to read it and narrate what they understand.

Note where the person hesitates, backtracks, or asks. Those points are the real readability problems — and they rarely coincide with what a style review would flag.

Interview Questions

  • How do you assess readability in a way two people would agree on?
  • What kind of comment is worth keeping?
  • When does extracting a function make the code worse?

Further Exploration

  • Martin, Robert C. Clean Code. Prentice Hall, 2008.
  • Ousterhout, John. A Philosophy of Software Design. Yaknyam Press, 2018 — disagrees with Clean Code on several points, and is worth reading alongside.
  • Beck, Kent. Implementation Patterns. Addison-Wesley, 2007.
Finished reading this document?Your progress is saved in this browser only.