Software Engineering

Clean Code Principles That Actually Matter

TL;DR

Focus on readability over cleverness, meaningful names, small functions with single responsibility, and appropriate abstraction levels. Avoid premature optimization and over-engineering. The best code is code that's easy to change.

January 15, 20269 min read
Clean CodeBest PracticesRefactoringCode QualitySoftware Design

After reviewing thousands of pull requests and maintaining codebases of all sizes, I've identified which clean code principles actually improve maintainability and which are cargo cult. This guide focuses on what matters.

The Core Philosophy

Clean code isn't about following rulesβ€”it's about communication. Code is read far more often than it's written. Martin Fowler (2018) captured this: "Any fool can write code that a computer can understand. Good programmers write code that humans can understand."

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    The Clean Code Pyramid                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                              β”‚
β”‚                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”                           β”‚
β”‚                         β”‚ Arch. β”‚  System design            β”‚
β”‚                       β”Œβ”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”                         β”‚
β”‚                       β”‚  Patterns  β”‚  Design patterns       β”‚
β”‚                     β”Œβ”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”                       β”‚
β”‚                     β”‚   Functions    β”‚  Single responsibilityβ”‚
β”‚                   β”Œβ”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”                     β”‚
β”‚                   β”‚      Names         β”‚  Clear intent       β”‚
β”‚                 β”Œβ”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”                   β”‚
β”‚                 β”‚       Formatting       β”‚  Consistency      β”‚
β”‚               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚
β”‚                                                              β”‚
β”‚              Foundation β†’ Most impactful at base             β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Insight

You can't have clean architecture without clean functions, and you can't have clean functions without clean names. Build from the bottom up.

Naming: The Foundation

Names Should Reveal Intent

# Bad: What does this do?
def calc(a, b, c):
    return a * b * (1 - c)
 
# Good: Intent is clear
def calculate_discounted_price(unit_price: float, quantity: int, discount_rate: float) -> float:
    return unit_price * quantity * (1 - discount_rate)

Use Domain Language

// Bad: Generic technical terms
interface DataObject {
  id: string;
  value: number;
  timestamp: Date;
}
 
// Good: Domain-specific terms
interface StockPrice {
  tickerSymbol: string;
  priceInCents: number;
  quotedAt: Date;
}

Avoid Misleading Names

// Bad: accountList is actually a Set
Set<Account> accountList = new HashSet<>();
 
// Bad: hp could mean anything
int hp = 120;
 
// Good: Clear and accurate
Set<Account> activeAccounts = new HashSet<>();
int healthPoints = 120;

Length Proportional to Scope

# Short scope = short name is fine
for i in range(10):
    print(i)
 
# Long scope = descriptive name needed
class OrderProcessor:
    def __init__(self):
        self.pending_order_count = 0
        self.processed_order_count = 0
 
# Function name should describe what it does
def u(x):  # Bad
    pass
 
def update_user_email_preferences(user: User, preferences: EmailPreferences):  # Good
    pass

Functions: Small and Focused

Single Responsibility

# Bad: Does too many things
def process_order(order: Order) -> None:
    # Validate
    if not order.items:
        raise ValueError("Empty order")
    if not order.customer.address:
        raise ValueError("No shipping address")
 
    # Calculate totals
    subtotal = sum(item.price * item.quantity for item in order.items)
    tax = subtotal * 0.08
    shipping = 5.99 if subtotal < 50 else 0
    total = subtotal + tax + shipping
 
    # Update inventory
    for item in order.items:
        inventory[item.sku] -= item.quantity
 
    # Charge payment
    payment_gateway.charge(order.customer.payment_method, total)
 
    # Send confirmation
    email_service.send(order.customer.email, f"Order confirmed: ${total}")
 
# Good: Single responsibility per function
def process_order(order: Order) -> OrderResult:
    validate_order(order)
    totals = calculate_order_totals(order)
    reserve_inventory(order.items)
    charge_result = process_payment(order.customer, totals.grand_total)
    send_order_confirmation(order, totals)
    return OrderResult(order_id=order.id, charged=totals.grand_total)

Appropriate Abstraction Level

# Bad: Mixed abstraction levels
def generate_report(data: list[dict]) -> str:
    # High level
    report = Report()
 
    # Suddenly low level
    html = "<html><head><style>"
    html += "table { border-collapse: collapse; }"
    html += "</style></head><body>"
 
    # Back to high level
    report.add_summary(data)
 
    # Low level again
    for row in data:
        html += f"<tr><td>{row['name']}</td><td>{row['value']}</td></tr>"
 
# Good: Consistent abstraction
def generate_report(data: list[dict]) -> str:
    report = Report()
    report.add_header()
    report.add_summary(calculate_summary(data))
    report.add_data_table(data)
    report.add_footer()
    return report.render()

Minimize Parameters

// Bad: Too many parameters
function createUser(
  firstName: string,
  lastName: string,
  email: string,
  phone: string,
  address: string,
  city: string,
  country: string,
  preferences: string[]
): User {
  // ...
}
 
// Good: Use an object
interface CreateUserRequest {
  name: { first: string; last: string };
  contact: { email: string; phone?: string };
  address: { street: string; city: string; country: string };
  preferences?: string[];
}
 
function createUser(request: CreateUserRequest): User {
  // ...
}

Common Mistake

Don't blindly extract every few lines into a function. Functions should represent meaningful units of work, not arbitrary line counts. A 20-line function that does one clear thing is better than 5 functions that fragment the logic.

Comments: When and How

Good Comments

# Explain WHY, not WHAT
# We use a 3-second timeout because the upstream service
# has known latency spikes during peak hours (see incident #1234)
response = client.fetch(url, timeout=3.0)
 
# Clarify complex business rules
# Tax exemption applies to non-profit organizations (501c3)
# registered before 2020, per IRS ruling 2019-42
if org.is_nonprofit and org.registration_year < 2020:
    tax_rate = 0
 
# Warn about consequences
# WARNING: This cache is shared across all instances.
# Clearing it will cause a thundering herd problem.
# Use the gradual_clear() method instead.
cache.clear()

Bad Comments

// Bad: States the obvious
i++; // Increment i
 
// Bad: Outdated comment (code changed, comment didn't)
// Calculate the average of three numbers
return (a + b) / 2;
 
// Bad: Commented-out code
// public void oldMethod() {
//     // This was the old implementation
//     doOldThing();
// }
 
// Bad: Journal comments (that's what git is for)
// Modified by John on 2024-01-15
// Fixed bug reported by Sarah

The Best Comment is No Comment

# Instead of this:
# Check if user is eligible for premium features
if user.subscription_level >= 2 and user.account_age_days > 30 and not user.is_suspended:
    enable_premium()
 
# Write self-documenting code:
def is_eligible_for_premium(user: User) -> bool:
    has_premium_subscription = user.subscription_level >= PREMIUM_TIER
    is_established_account = user.account_age_days > MINIMUM_ACCOUNT_AGE
    is_in_good_standing = not user.is_suspended
    return has_premium_subscription and is_established_account and is_in_good_standing
 
if is_eligible_for_premium(user):
    enable_premium()

Error Handling

Use Exceptions for Exceptional Cases

# Bad: Using exceptions for control flow
def find_user(user_id: str) -> User:
    try:
        return database.get(user_id)
    except:
        return None  # Swallows all errors
 
# Good: Clear error handling
def find_user(user_id: str) -> User | None:
    """Returns None if user not found, raises on database errors."""
    try:
        return database.get(user_id)
    except UserNotFoundError:
        return None
    except DatabaseConnectionError:
        logger.error(f"Database error looking up user {user_id}")
        raise

Fail Fast

// Bad: Continue with bad data
function processPayment(amount: number, currency: string): PaymentResult {
  // Process even with invalid input, causing weird bugs later
  const convertedAmount = amount || 0;
  // ...
}
 
// Good: Validate early
function processPayment(amount: number, currency: string): PaymentResult {
  if (amount <= 0) {
    throw new InvalidAmountError(`Amount must be positive, got: ${amount}`);
  }
  if (!SUPPORTED_CURRENCIES.includes(currency)) {
    throw new UnsupportedCurrencyError(`Currency not supported: ${currency}`);
  }
  // Now we know we have valid data
  // ...
}

Provide Context

# Bad: Generic error
raise Exception("Failed")
 
# Good: Actionable error message
raise PaymentProcessingError(
    f"Failed to charge card ending in {card_last_four} for ${amount:.2f}. "
    f"Gateway response: {gateway_response.error_code} - {gateway_response.message}. "
    f"Transaction ID: {transaction_id}"
)

Code Organization

The Newspaper Metaphor

Robert Martin's (2008) "newspaper metaphor" suggests code should read like a newspaper: important concepts at the top, details below.

# File: order_service.py
 
# Public interface at the top (the headline)
class OrderService:
    def place_order(self, cart: Cart, customer: Customer) -> Order:
        """Place an order from a shopping cart."""
        self._validate_cart(cart)
        order = self._create_order(cart, customer)
        self._process_payment(order)
        self._send_confirmation(order)
        return order
 
    def cancel_order(self, order_id: str) -> None:
        """Cancel an existing order."""
        order = self._get_order(order_id)
        self._validate_cancellation(order)
        self._refund_payment(order)
        self._restore_inventory(order)
 
    # Private implementation details below (the body paragraphs)
    def _validate_cart(self, cart: Cart) -> None:
        if cart.is_empty():
            raise EmptyCartError()
        for item in cart.items:
            if not self._is_in_stock(item):
                raise OutOfStockError(item.sku)
 
    def _create_order(self, cart: Cart, customer: Customer) -> Order:
        # ...
 
    def _process_payment(self, order: Order) -> None:
        # ...

Cohesion and Coupling

// Bad: Low cohesion - class does unrelated things
class UserManager {
  createUser(data: UserData): User { /* ... */ }
  sendEmail(to: string, body: string): void { /* ... */ }
  generateReport(users: User[]): Report { /* ... */ }
  compressImage(image: Buffer): Buffer { /* ... */ }
}
 
// Good: High cohesion - class has focused responsibility
class UserRepository {
  create(data: UserData): User { /* ... */ }
  findById(id: string): User | null { /* ... */ }
  update(user: User): void { /* ... */ }
  delete(id: string): void { /* ... */ }
}
 
class EmailService {
  send(message: EmailMessage): void { /* ... */ }
  sendBulk(messages: EmailMessage[]): void { /* ... */ }
}

Pragmatic Cleanliness

Don't Over-Engineer

# Over-engineered: Factory for a single implementation
class DatabaseConnectionFactory:
    def create_connection(self, config: Config) -> DatabaseConnection:
        return PostgresConnection(config)
 
# Just use the thing directly
connection = PostgresConnection(config)

Tolerate Some Duplication

# Don't abstract too early
# If you have two similar functions, it's often fine
def create_admin_user(name: str, email: str) -> User:
    user = User(name=name, email=email, role="admin")
    user.permissions = ADMIN_PERMISSIONS
    notify_admin_created(user)
    return user
 
def create_regular_user(name: str, email: str) -> User:
    user = User(name=name, email=email, role="user")
    user.permissions = DEFAULT_PERMISSIONS
    send_welcome_email(user)
    return user
 
# Premature abstraction makes code harder to understand
# Wait until you have three+ cases before abstracting

Optimize for Reading, Not Writing

// Clever one-liner (hard to read)
const result = data.filter(x => x.active).reduce((a, b) => ({...a, [b.id]: b.items.flatMap(i => i.tags).filter((t, i, arr) => arr.indexOf(t) === i)}), {});
 
// Clear multi-line version (easy to read)
const activeItems = data.filter(item => item.active);
 
const result: Record<string, string[]> = {};
for (const item of activeItems) {
  const allTags = item.items.flatMap(i => i.tags);
  const uniqueTags = [...new Set(allTags)];
  result[item.id] = uniqueTags;
}

Conclusion

Clean code principles that consistently matter:

  1. Names reveal intent - Spend time choosing good names
  2. Functions do one thing - Keep them small and focused
  3. Comments explain why - Not what the code does
  4. Errors are informative - Help future debuggers
  5. Code reads top-down - Important stuff first
  6. Consistency over perfection - Follow team conventions
  7. Simplicity over cleverness - Boring code is good code

The goal isn't perfect codeβ€”it's code that's easy to change when requirements change. And they always do.


References

Martin, R. C. (2008). Clean code: A handbook of agile software craftsmanship. Prentice Hall.

Fowler, M. (2018). Refactoring: Improving the design of existing code (2nd ed.). Addison-Wesley.

Thomas, D., & Hunt, A. (2019). The pragmatic programmer: Your journey to mastery (20th anniversary ed.). Addison-Wesley.

McConnell, S. (2004). Code complete: A practical handbook of software construction (2nd ed.). Microsoft Press.


Want to improve your team's code quality? Get in touch to discuss code review practices and standards.

Frequently Asked Questions

OR

Osvaldo Restrepo

Senior Full Stack AI & Software Engineer. Building production AI systems that solve real problems.