Clean Code
“Clean code” is one of those phrases that sounds universal until you ask three engineers to define it. Then the debate starts:
- short functions or expressive functions?
- reuse everywhere or keep code local?
- 2 spaces, 4 spaces, tabs, semicolons, no semicolons?
There is no single global definition. What counts as clean code depends on the project, the team, and the constraints around the codebase.
Start With A Style Guide
If you are working on a team, the best definition of clean code is the style guide the team has already agreed on.
A style guide is a shared set of rules for formatting and structuring code.
That style guide should cover more than formatting. It should also answer questions like:
- when to extract a helper
- when to prefer composition over inheritance
- how much abstraction is acceptable in a component or module
- which lint rules are mandatory versus optional
If the project does not have one yet, adopt a small, explicit set of rules instead of relying on vibes.
Automate The Boring Parts
Manual code review is a poor way to enforce style. Use tools that make the common rules automatic.
A linter checks code for style and correctness rules. Some linters can also auto-fix formatting issues.
A practical setup often looks like this:
- format on save for whitespace and line length
- lint in CI for correctness and consistency
- reject changes that bypass the project’s standards
Examples that are still broadly useful:
- ESLint for JavaScript and TypeScript
- Prettier for formatting markdown, JSON, YAML, and HTML
- language-specific formatters or IDE tooling for languages like Java or C++
The exact tools matter less than the fact that the rules are enforced consistently.
Why People Disagree About “Clean”
Different teams optimize for different pain points. One team may care most about readability for new hires. Another may care about keeping refactors low risk. A third may care about minimizing change churn in a huge codebase.
That is why “clean code” is not a universal aesthetic. It is a local optimization problem.
A codebase with lots of tiny functions may be clearer if the logic is truly modular. The same codebase may become worse if the abstractions are just thin wrappers that force readers to jump around.
The rule is not “more abstraction is better.” The rule is “make the code easier for the next person to change.”
A Better Mental Model
Instead of asking “is this clean?”, ask:
- Is it obvious what this code does?
- Will I understand it again in six months?
- Is the abstraction doing real work?
- Does the file follow the project’s conventions?
- Is the logic simple enough to stay local?
That checklist is more useful than trying to satisfy an abstract purity test.
A Practical Example
Suppose your team likes explicit names, shallow nesting, and predictable file structure. A clean change would usually:
- keep the main control flow easy to scan
- extract only the logic that has a real reuse or readability benefit
- avoid renaming things just to sound clever
- keep the implementation consistent with the surrounding code
For example, if the rest of the project uses long descriptive names, introducing terse single-letter variables is not “elegant.” It is friction.
If the rest of the project uses small focused helpers, inlining a 100-line function is not “simple.” It is just moved complexity.
What I Would Actually Do
When I join a codebase, I usually follow this order:
- Read the existing style guide or lint configuration.
- Match the code around my change.
- Keep new abstractions minimal until there is a clear reason to introduce them.
- Prefer explicit code over clever code when both are equally correct.
- Leave the code easier to modify than I found it.
That is a more useful definition of clean code than any slogan.
Summary
- Clean code is local to the project, not universal.
- Start with a style guide and enforce it with tooling.
- Optimize for readability and changeability, not for purity.
- Add abstractions when they reduce real complexity, not because duplication feels philosophically wrong.