Clean Code Principles: Writing Code Your Future Self Will Thank You For
A practical clean code guide with real examples — learn naming conventions, function design, comments, error handling, and code structure that makes code a joy to maintain.
Get more content like this on Telegram!
Daily AI tips, notes & resources — free
Clean Code Principles: Writing Code Your Future Self Will Thank You For
I once inherited a codebase from a developer who had left the company. The code worked — I could verify that because the tests passed. But reading it was like deciphering another language.
Variables named d, tmp, data2. Functions 300 lines long. Comments that said things like // increment i next to i++. Business logic mixed with database calls mixed with UI rendering in the same file.
It took me three months to understand that codebase well enough to make a simple change safely. Three months.
The original developer wasn't malicious. They were smart. But they wrote code for the computer — and forgot that code is primarily read by humans.
Clean code is code that communicates. In this guide, you'll learn the specific principles that transform code from a puzzle into prose that any developer can read, understand, and confidently change.
What Clean Code Is (and Isn't)
Clean code is not:
- Perfect code
- Over-engineered code with layers of abstraction
- Code with extensive documentation
- Code with zero duplication at all costs
Clean code is:
- Code that reads like well-written prose
- Code where the intent is immediately clear
- Code that is easy to change without breaking other things
- Code that does what it says and says what it does
Robert Martin's definition in Clean Code: "Clean code reads like well-written prose." That's the bar.
Principle 1: Meaningful Names
Variables and Constants
Names should reveal intent. The time spent choosing a good name is saved many times over in reading time.
// Unclear
const d = 86400;
const yyyymmdd = moment().format('YYYY/MM/DD');
const theList = getUsers();
// Clear
const SECONDS_PER_DAY = 86400;
const formattedDate = moment().format('YYYY/MM/DD');
const activeUsers = getUsers();
Rules for naming:
- Use pronounceable names (
startDatenotstrtDt) - Use searchable names (avoid single letters except loop counters)
- Avoid encodings (
userListnotlstUsers,UserClassnotCUser) - Use nouns for variables, verbs for functions
Functions
Function names should describe what the function does, starting with a verb:
// Unclear
function handle(user) { }
function data() { }
function process(x, y) { }
// Clear
function sendWelcomeEmail(user) { }
function fetchActiveUsers() { }
function calculateShippingCost(weight, destination) { }
Avoid Mental Mapping
Don't make the reader translate your names mentally:
// Forces mental translation
for (let i = 0; i < users.length; i++) {
// What is 'i' here? You have to track it.
}
// Self-explanatory
for (const user of users) {
// Reads naturally: "for each user..."
}
Principle 2: Functions That Do One Thing
The Single Responsibility Principle, applied to functions: a function should do one thing, do it well, and do it only.
How to Test This
Can you describe the function in one sentence without using "and"?
// Does three things — note all the "and"s
async function processUserRegistration(userData) {
// Validate input AND hash password AND create user AND send email AND log event
const errors = validateInput(userData);
if (errors.length) throw new Error(errors.join(', '));
const hashedPassword = await bcrypt.hash(userData.password, 10);
const user = await db.users.create({ ...userData, password: hashedPassword });
await emailService.sendWelcome(user.email);
await analytics.track('user_registered', { userId: user.id });
return user;
}
// Each function does one thing
async function processUserRegistration(userData) {
validateRegistrationInput(userData);
const user = await createUser(userData);
await notifyNewUser(user);
return user;
}
async function createUser(userData) {
const hashedPassword = await hashPassword(userData.password);
return db.users.create({ ...userData, password: hashedPassword });
}
async function notifyNewUser(user) {
await Promise.all([
emailService.sendWelcome(user.email),
analytics.track('user_registered', { userId: user.id }),
]);
}
The second version is longer in total lines, but each individual function is short, testable in isolation, and readable at a glance.
Function Arguments
The ideal number of function arguments is zero. One is fine. Two is acceptable. Three requires a very good reason. More than three — use an options object.
// Too many arguments — hard to call, easy to confuse order
function createUser(name, email, password, role, isActive, avatarUrl) {}
// Better — named options object
function createUser({ name, email, password, role = 'user', isActive = true, avatarUrl }) {}
// Call site is now self-documenting
createUser({
name: 'Alice',
email: 'alice@example.com',
password: 'secret',
role: 'admin'
});
Principle 3: Comments That Add Value
The worst comment is one that restates what the code clearly says:
// Bad: restates the code
i = i + 1; // increment i
// Bad: explains obvious syntax
// Create a new Date object
const now = new Date();
// Good: explains WHY, not what
// Prices must be stored as integers (cents) to avoid floating-point errors
const priceInCents = Math.round(price * 100);
// Good: explains non-obvious business rule
// Regulatory requirement: refunds must be processed within 72 hours
const REFUND_DEADLINE_HOURS = 72;
When comments are valuable:
- Legal notices (copyright headers)
- Non-obvious intent or business constraints
- Warnings about consequences (
// Do not cache — contains PII) - TODOs with context and owner (
// TODO(alice): remove after API v2 migration complete)
The best code has very few comments because the code is self-explanatory. If you find yourself writing many comments, that's usually a sign to improve naming and structure.
Principle 4: Error Handling
Clean error handling separates the "happy path" from error cases and provides useful information when things go wrong.
// Anti-pattern: swallowing errors silently
function getUser(id) {
try {
return db.findUser(id);
} catch (e) {
return null; // Bug: we lose all information about what went wrong
}
}
// Anti-pattern: generic error messages
throw new Error('Something went wrong');
// Clean: specific, actionable errors
function getUser(id) {
const user = db.findUser(id);
if (!user) {
throw new NotFoundError(`User with id ${id} not found`);
}
return user;
}
Don't Return Null When You Can Return an Empty Result
// Forces null checks everywhere
function getRecentOrders(userId) {
const orders = db.findOrders(userId);
return orders.length > 0 ? orders : null;
}
// Better: consistent type, no null checks needed
function getRecentOrders(userId) {
return db.findOrders(userId); // returns [] if none — always an array
}
Principle 5: Code Organization and Structure
Keep Related Code Together
Functions that are called together should be defined near each other. In a file handling user authentication, the login and logout functions should be adjacent.
Law of Demeter — Don't Talk to Strangers
// Violates Law of Demeter — calling through multiple objects
const city = user.getAddress().getZipCode().getCity();
// Better — ask the user directly
const city = user.getCity();
Each unit of code should only talk to its immediate dependencies, not reach through them.
File and Module Organization
// Anti-pattern: one massive file
utils.js // 2000 lines of miscellaneous functions
// Clean: focused modules
auth/
login.ts
logout.ts
passwordReset.ts
users/
createUser.ts
updateUser.ts
deleteUser.ts
Our web developer roadmap covers project structure conventions for different frameworks and team sizes.
Principle 6: Don't Repeat Yourself (Applied Carefully)
DRY doesn't mean "never write similar code twice." It means "every piece of knowledge should have a single authoritative representation."
The key question: are these two similar-looking pieces of code representing the same concept, or two different concepts that happen to look similar right now?
// These look the same — but represent different business rules
// Don't blindly combine them
function validateSignupEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function validateContactEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// If the business rules genuinely diverge (e.g., signup only allows company emails),
// you'll be glad you kept them separate. Extract a shared helper only if
// the rule is truly the same concept.
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
The rule of three: duplicate once if needed. On the third duplication, extract.
Applying Clean Code at Work
You don't need to refactor your entire codebase at once. The Boy Scout Rule: "Leave the campground cleaner than you found it."
Every time you touch a file:
- Rename one unclear variable you notice
- Extract one function that does multiple things
- Delete one comment that restates the code
- Fix one typo in a variable name
Applied consistently, this gradually improves any codebase without requiring dedicated refactoring sprints.
For the debugging dimension of code quality, see our debugging guide, which covers how clean code makes bugs easier to find and fix.
Frequently Asked Questions
What is clean code and why does it matter?
Clean code is easy to read, understand, and modify by any developer. It matters because developers spend ~10× more time reading code than writing it. Code that's hard to read slows every future change.
What is the most important clean code principle?
Meaningful naming. A well-named variable communicates intent without a comment. If you need a comment to explain what a variable is, it's poorly named.
How long should functions be?
5–10 lines ideally. A function should do exactly one thing and fit on one screen. If you need "and" to describe it, it does too many things.
Should I write comments in my code?
Comments should explain WHY, not WHAT. If code needs a comment to explain what it does, rename variables or extract a better-named function instead.
What is the DRY principle?
Don't Repeat Yourself — every piece of knowledge should have a single representation. But don't over-apply it: similar-looking code can represent different concepts. Use the rule of three: extract on the third duplication.
Frequently Asked Questions
AiTechWorlds Team
✓ Verified WriterThe AiTechWorlds team is passionate about AI, technology, and education. We create high-quality, research-backed content to help you learn, grow, and succeed in the modern digital world.
Related Articles
15 Coding Habits That Separate Senior Developers from Juniors
Discover the coding habits senior developers follow every day — from writing readable code to debugging smarter — that separate pros from beginners.
Understanding APIs: A Beginner's Story About How Apps Talk
API tutorial for beginners — understand what APIs are, how REST APIs work, HTTP methods, JSON, authentication, and how to call APIs in JavaScript with real examples.
The Python Libraries Every Developer Must Know in 2025
The essential Python libraries for 2025: from requests and pandas to FastAPI and LangChain — what each does, when to use it, and how to get started quickly.
The 10 VS Code Extensions That Make You Code Twice as Fast
The best VS Code extensions in 2025 that genuinely boost productivity — from AI code completion to live sharing, error highlighting, and formatting automation.