DEV Community

Orbit Websites
Orbit Websites

Posted on

Mastering Clean Code: Essential Principles Every Developer Should Know

Mastering Clean Code: Essential Principles Every Developer Should Know

We’ve all been there: opening a file, seeing a 500-line function with nested loops, unclear variable names, and side effects everywhere. You stare at it, heart sinking, wondering who wrote this mess—only to find your own name at the top from six months ago.

Clean code isn’t about being “perfect.” It’s about writing code that’s easy to read, change, and debug—especially when you’re not in the right headspace. Whether you're working solo or on a team, clean code saves time, reduces bugs, and makes maintenance less painful. Let’s go over the principles that actually matter in real-world development.


1. Meaningful Names > Clever Names

Your variable, function, and class names should tell you why they exist, not just what they do.

Bad:

def process_data(d):
    r = []
    for i in d:
        if i[2] > 40:
            r.append(i[0])
    return r
Enter fullscreen mode Exit fullscreen mode

What is d? What’s i[2]? Why 40? This is unreadable.

Good:

def get_senior_employees(employees):
    senior_employees = []
    for employee in employees:
        if employee.age > 40:
            senior_employees.append(employee.name)
    return senior_employees
Enter fullscreen mode Exit fullscreen mode

Now it’s obvious. No comments needed. The code is the documentation.

Pro tip: If you find yourself writing a comment to explain a variable, consider renaming it instead.


2. Functions Should Do One Thing — And One Thing Well

A function should have a single responsibility. If it’s doing multiple things, split it.

Bad:

function saveUser(userData) {
  if (!userData.email || !userData.name) {
    throw new Error("Missing required fields");
  }

  const user = {
    id: generateId(),
    email: userData.email.trim().toLowerCase(),
    name: userData.name.trim(),
    createdAt: new Date(),
  };

  const db = getDatabase();
  db.users.insert(user);

  sendWelcomeEmail(user.email);

  return user;
}
Enter fullscreen mode Exit fullscreen mode

This function validates, formats, saves, and sends an email. That’s four responsibilities.

Good:

function createUser(userData) {
  validateUserData(userData);
  return {
    id: generateId(),
    email: userData.email.trim().toLowerCase(),
    name: userData.name.trim(),
    createdAt: new Date(),
  };
}

function saveUser(userData) {
  const user = createUser(userData);
  getDatabase().users.insert(user);
  sendWelcomeEmail(user.email);
  return user;
}
Enter fullscreen mode Exit fullscreen mode

Now createUser only builds the object. saveUser orchestrates. Each is easier to test and modify.

Rule of thumb: If you can’t name a function without using “and” or “then,” it’s doing too much.


3. Minimize Side Effects

A function with side effects changes something outside its scope—like modifying a global variable, writing to a file, or mutating input.

Side effects aren’t evil, but they should be obvious and isolated.

Bad:

function calculateTotal(items) {
  let total = 0;
  for (let i = 0; i < items.length; i++) {
    total += items[i].price;
    items[i].processed = true; // 🚩 mutating input
  }
  return total;
}
Enter fullscreen mode Exit fullscreen mode

Now the caller’s data is changed. Surprise!

Good:

function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.price, 0);
}

// Handle side effects separately
function markAsProcessed(items) {
  return items.map(item => ({ ...item, processed: true }));
}
Enter fullscreen mode Exit fullscreen mode

Now calculateTotal is pure. It doesn’t mutate anything. Side effects are explicit.

Bonus: Pure functions are easier to test and reason about.


4. Avoid Deep Nesting

Deeply nested if statements or loops make code hard to follow. Flatten when you can.

Bad:

if user:
    if user.is_active:
        if user.has_permission:
            if user.subscription_active:
                grant_access()
Enter fullscreen mode Exit fullscreen mode

This is the “pyramid of doom.” Hard to read, harder to test.

Good:

if not user:
    return
if not user.is_active:
    return
if not user.has_permission:
    return
if not user.subscription_active:
    return

grant_access()
Enter fullscreen mode Exit fullscreen mode

Or even better:

def can_grant_access(user):
    return user and user.is_active and user.has_permission and user.subscription_active

if can_grant_access(user):
    grant_access()
Enter fullscreen mode Exit fullscreen mode

Early returns or guard clauses keep the happy path clean.


5. Comments: Use Sparingly, and Only When Necessary

Comments aren’t a substitute for clear code. In fact, outdated comments are worse than no comments.

Bad:

// Loop through users and add to list if active
users.forEach(u => {
  if (u.active) {
    activeUsers.push(u);
  }
});
Enter fullscreen mode Exit fullscreen mode

This comment is redundant. The code already says that.

Good:

// We exclude trial users due to billing constraints (see JIRA-1234)
if (user.isActive && !user.isTrial) {
  activateService(user);
}
Enter fullscreen mode Exit fullscreen mode

Now the comment explains why, not what. That’s valuable.

When in doubt: rewrite the code to be self-ex


Factual

Top comments (0)