Clean Code Principles: Three Essential Rules

These principles are designed for developers to copy into their AI coding guidelines. They instruct AI systems to generate code that is clean, maintainable, and easy to read, test, and extend.

Clean Code Guidelines

- Guard Clauses / Fail-Fast: Handle invalid or special cases early and return immediately.
- Use switch, pattern matching, strategy, or a lookup table for branching on one discriminator.
- Command–Query Separation (CQS): A function either returns data or causes effects, never both.

Guard Clauses / Fail-Fast

Rule: Guard Clauses / Fail-Fast: Handle invalid or special cases early and return immediately.

❌ Bad Example: Deeply nested conditions

function calculateDiscount(price, customerType, coupon) {
  if (price > 0) {
    if (customerType === 'premium') {
      if (coupon === 'SAVE20') {
        return price * 0.8; // 20% discount
      } else {
        return price * 0.9; // 10% discount
      }
    } else {
      return price; // no discount
    }
  } else {
    throw new Error('Invalid price');
  }
}

Problems:

  • Hard to follow the main flow
  • Error handling is buried at the bottom
  • More cognitive load to understand

✅ Good Example: Guard clauses with early returns

function calculateDiscountWithGuards(price, customerType, coupon) {
  if (price <= 0) throw new Error('Invalid price');
  if (customerType !== 'premium') return price;
  
  return coupon === 'SAVE20' ? price * 0.8 : price * 0.9;
}

Benefits:

  • Invalid inputs handled immediately
  • Main logic is clear and at the top level
  • Reduced nesting and cognitive complexity

Branching on One Discriminator

Rule: Use switch, pattern matching, strategy, or a lookup table for branching on one discriminator.

❌ Bad Example: Multiple if-else statements

function getShippingCost(shippingMethod) {
  if (shippingMethod === 'standard') {
    return 5.99;
  } else if (shippingMethod === 'express') {
    return 12.99;
  } else if (shippingMethod === 'overnight') {
    return 24.99;
  } else if (shippingMethod === 'pickup') {
    return 0;
  } else {
    throw new Error('Invalid shipping method');
  }
}

Problems:

  • Repetitive if-else chain structure
  • Hard to maintain when adding new methods
  • Not easily extensible

✅ Good Example: Lookup table approach

function getShippingCost(shippingMethod) {
  const shippingCosts = {
    standard: 5.99,
    express: 12.99,
    overnight: 24.99,
    pickup: 0
  };
  
  if (!(shippingMethod in shippingCosts)) {
    throw new Error('Invalid shipping method');
  }
  
  return shippingCosts[shippingMethod];
}

✅ Alternative: Switch statement

function getShippingCost(shippingMethod) {
  switch (shippingMethod) {
    case 'standard':
      return 5.99;
    case 'express':
      return 12.99;
    case 'overnight':
      return 24.99;
    case 'pickup':
      return 0;
    default:
      throw new Error('Invalid shipping method');
  }
}

Benefits:

  • Cleaner, more readable code
  • Clear mapping between discriminator and action
  • Better performance with lookup tables

Command–Query Separation (CQS)

Rule: Command–Query Separation (CQS): A function either returns data or causes effects, never both.

❌ Bad Example: Function that both queries and commands

function removeAndCountItems(array, item) {
  let count = 0;
  for (let i = array.length - 1; i >= 0; i--) {
    if (array[i] === item) {
      array.splice(i, 1); // Mutates array (command)
      count++;
    }
  }
  return count; // Returns data (query)
}

Problems:

  • Hard to predict if function will change state
  • Difficult to test and reason about
  • Unclear intent from function name

✅ Good Example: Separated commands and queries

// Query: Returns data without mutation
function countItems(array, item) {
  return array.filter(x => x === item).length;
}

// Command: Only mutates, returns void
function removeAllItems(array, item) {
  for (let i = array.length - 1; i >= 0; i--) {
    if (array[i] === item) {
      array.splice(i, 1);
    }
  }
}

Benefits:

  • Clear separation of concerns
  • Predictable behavior - queries are safe, commands have effects
  • Easier to test and debug
  • Functions have single, clear purpose

References: