·5 min read

How to Write Clean Code That Other Developers Won't Hate You For

Clean code isn't about patterns or architecture. It's about the next person who reads it — usually you, 6 months later.

ProgrammingBest PracticesCareerCode Quality

Clean code isn't about design patterns. It's not about architecture diagrams or SOLID principles or whatever the latest framework evangelist is selling.

Clean code is about one person: the next developer who has to read it. Usually, that's you — six months later, at 11pm, trying to fix a bug in code you wrote and don't remember writing.

Here are five rules that will make your code readable, maintainable, and not hated by everyone who touches it after you.

Rule 1: Name things by what they do

The single biggest difference between clean code and messy code is naming. Bad names force the reader to investigate. Good names let them skip the investigation.

Bad:

const data = await fetchData()
const result = process(data)
const items = result.filter(x => x.active)

What is data? What does process do? What is x? To answer any of these questions, you have to read the implementation. That's friction.

Good:

const users = await fetchUsers()
const validatedUsers = validateUsers(users)
const activeUsers = validatedUsers.filter(user => user.isActive)

Now you don't need to read the implementation. The names tell the story. fetchUsers gets users. validateUsers validates them. The filter keeps active ones. Done.

The rule: if someone has to open your function to understand what it does, the name is wrong.

Rule 2: Functions do one thing

This is the most violated rule in software development. Functions that do "one thing AND another thing AND maybe a third thing" are why codebases become unmaintainable.

The test is simple: can you describe what the function does without using the word "and"?

Bad:

function handleUser(data) {
  // Validates the user
  if (!data.email.includes('@')) throw new Error('Invalid email')
  
  // AND saves to database
  await db.users.insert(data)
  
  // AND sends a welcome email
  await email.send(data.email, 'Welcome!')
  
  // AND logs the analytics event
  analytics.track('user_signed_up', { email: data.email })
}

This function does four things. When something breaks — and it will — you don't know which part failed. When you need to change one thing, you risk breaking the other three.

Good:

function createUser(data) {
  validateEmail(data.email)
  const user = await saveUser(data)
  await sendWelcomeEmail(user)
  trackSignup(user)
}

Now createUser is an orchestrator. Each step is a separate function with one job. If welcome emails break, you fix sendWelcomeEmail without touching anything else. If validation changes, you update validateEmail.

Small functions are easy to test, easy to name, and easy to reason about.

Rule 3: Comments are a code smell

This one is controversial. Here's my position: if you need a comment to explain what the code does, the code is wrong. Rewrite it.

Comments rot. Code changes, comments don't. Six months from now, the comment says one thing and the code does another — and the next developer trusts the comment.

Bad:

// Check if user is admin and has paid subscription
if (user.role === 1 && user.plan === 'pro' && !user.cancelled) {
  showPremiumFeatures()
}

Good:

if (isActiveAdmin(user)) {
  showPremiumFeatures()
}
 
function isActiveAdmin(user) {
  return user.role === 'admin' && isSubscriptionActive(user)
}

Now the code explains itself. No comment needed. When the logic changes, the function name and body change together.

When comments are okay: explaining why, not what. If you're working around a third-party bug, or implementing a non-obvious business rule, a comment explaining the reasoning is valuable. But "what" should always be in the code.

Rule 4: Early returns kill nesting

Deep nesting is the enemy of readability. Every level of nesting adds cognitive load. By level four, nobody can follow the logic.

Bad (the arrow):

function processOrder(order) {
  if (order) {
    if (order.items.length > 0) {
      if (order.user) {
        if (order.user.isActive) {
          // actual logic here
        }
      }
    }
  }
}

Good (early returns):

function processOrder(order) {
  if (!order) return
  if (order.items.length === 0) return
  if (!order.user) return
  if (!order.user.isActive) return
 
  // actual logic here, at the top level
}

The early return pattern flattens code. Each condition is checked and dismissed at the top. The "happy path" — the actual logic — sits at the top level, easy to read and modify.

If you see code shaped like an arrow pointing right, refactor with early returns. It's a 5-minute change that makes the function dramatically more readable.

Rule 5: Kill magic numbers

Numbers without context are dangerous. 86400 — is that seconds in a day? A timeout? An ID? You can't know without investigating.

Bad:

setTimeout(refreshToken, 3600000)
if (response.status === 429) retryAfter(60)
const maxRetries = 3

What is 3600000? Why 429? Why 60? Why 3?

Good:

const ONE_HOUR_MS = 60 * 60 * 1000
const HTTP_TOO_MANY_REQUESTS = 429
const RETRY_DELAY_SECONDS = 60
const MAX_RETRIES = 3
 
setTimeout(refreshToken, ONE_HOUR_MS)
if (response.status === HTTP_TOO_MANY_REQUESTS) retryAfter(RETRY_DELAY_SECONDS)

Now the code documents itself. Constants turn magic numbers into named, searchable, understandable values. When the retry delay changes from 60 to 90 seconds, you update one constant — not hunt through the codebase for 60 and hope you found the right one.

The rule: if a number has meaning beyond its value, it's a constant.

What actually matters

These five rules sound simple. They are. The hard part isn't knowing them — it's applying them consistently, especially under deadline pressure.

Here's the truth: writing clean code takes slightly longer in the moment. Maybe 10% more time when you're writing it. But it saves 50%+ time when you maintain it, debug it, or extend it. And someone always has to maintain, debug, or extend it.

The code I'm most proud of isn't the cleverest or the most technically impressive. It's the code that another developer opened, understood in 2 minutes, and modified without asking me a single question. That's the bar.

Clean code isn't written for the compiler. It's written for the human who reads it next.

Want code that's clean and maintainable?

I write code that other developers can actually work with — named clearly, structured simply, documented by the code itself. Let's talk.