Clean Code Practices Cheatsheet

A comprehensive reference for writing readable, maintainable, and robust code.


Naming Conventions

Good names replace the need for comments. A name should reveal intent, avoid ambiguity, and be easy to search for.

Variables

  • Use nouns that describe what the value represents.
  • Avoid single-letter names except in tiny loop scopes.

Don’t:

d = 7  # elapsed time in days

Do:

elapsed_days = 7

Functions

  • Use verbs or verb phrases that describe the action performed.

Don’t:

function data(url) { /* ... */ }

Do:

function fetchUserProfile(url) { /* ... */ }

Classes

  • Use singular nouns. Avoid vague suffixes like Manager, Processor, or Data.

Don’t:

class DataProcessor:
    pass

Do:

class InvoiceParser:
    pass

Constants

  • Use UPPER_SNAKE_CASE. The name should explain the meaning, not the value.

Don’t:

T = 86400

Do:

SECONDS_PER_DAY = 86400

Booleans

  • Phrase as a yes/no question using prefixes like is, has, can, should.

Don’t:

let login = true;

Do:

let isLoggedIn = true;

Functions

Single Responsibility

  • A function should do one thing, do it well, and do it only.

Don’t:

def process_order(order):
    validate(order)
    charge_payment(order)
    send_email(order)

Do:

def validate_order(order):
    # only validation logic
    ...

Keep Functions Small

  • Aim for 5-20 lines. If a function needs a comment to explain a section, extract that section into its own function.

Limit Parameters

  • Ideal: 0-2 parameters. If you need more, group them into an object or data class.

Don’t:

function createUser(name, email, age, role, dept) { }

Do:

function createUser(userDetails) { }

No Side Effects

  • A function that claims to do one thing should not secretly modify global state, mutate arguments, or perform I/O.

Command-Query Separation

  • A function should either do something (command) or answer something (query), never both.

Don’t:

def set_and_check_age(user, age):
    user.age = age
    return user.age >= 18

Do:

def set_age(user, age):
    user.age = age

def is_adult(user):
    return user.age >= 18

DRY (Don’t Repeat Yourself)

  • If you see the same logic in two places, extract it into a shared function.

Comments

When to Comment

  • Legal or license headers.
  • Explanation of why, not what (intent behind a non-obvious decision).
  • Warnings of consequences (e.g., “This test takes 10 minutes to run”).
  • TODO markers for known technical debt.

When NOT to Comment

  • To restate what the code already says.
  • To explain bad naming – rename instead.
  • To keep old code around – use version control.

Good Comments

# Using binary search here because the dataset exceeds 1M records
# and linear scan caused p99 latency spikes.
index = bisect_left(sorted_ids, target_id)

Bad Comments

# increment i by 1
i += 1

# default constructor
def __init__(self):
    pass
  • Commented-out code: delete it. Git remembers.
  • Journal comments at the top of files: let the commit log handle history.

Error Handling

Use Exceptions Over Error Codes

  • Error codes force callers into deeply nested if chains.

Don’t:

result = withdraw(account, amount)
if result == -1:
    # handle insufficient funds

Do:

try:
    withdraw(account, amount)
except InsufficientFundsError as e:
    notify_user(e.message)

Don’t Return Null

  • Returning null forces every caller to add a null check. Return an empty collection, a default object, or raise an exception instead.

Don’t:

function getUsers() {
  if (noResults) return null;
}

Do:

function getUsers() {
  if (noResults) return [];
}

Fail Fast

  • Validate inputs at the boundary. Catch problems as early as possible rather than letting bad data propagate.

Write Meaningful Error Messages

  • Include what failed, why it failed, and how to fix it when possible.

Don’t:

raise ValueError("Invalid input")

Do:

raise ValueError(f"Age must be 0-150, got {age}")

Code Formatting

Consistent Indentation

  • Pick a style (2 spaces, 4 spaces, tabs) and enforce it project-wide with a formatter.

Vertical Spacing

  • Use blank lines to separate logical sections within a file: imports, class definitions, methods.
  • Keep related lines together – do not scatter them across the file.

Line Length

  • Keep lines under 80-120 characters. Long lines force horizontal scrolling and reduce readability.
  • Declare variables close to where they are used.
  • Order methods so that callers appear above callees (step-down rule).
  • Keep related functions in the same file or module.

SOLID Principles

S – Single Responsibility Principle

  • A class should have only one reason to change. One job, one owner.

O – Open/Closed Principle

  • Open for extension, closed for modification. Add new behavior by adding new code, not changing existing code.

L – Liskov Substitution Principle

  • Subtypes must be usable in place of their parent types without breaking behavior.

I – Interface Segregation Principle

  • No client should be forced to depend on methods it does not use. Prefer many small interfaces over one large one.

D – Dependency Inversion Principle

  • Depend on abstractions, not concretions. High-level modules should not import from low-level modules directly.
# Don't: high-level code depends on a concrete database
class OrderService:
    def __init__(self):
        self.db = MySQLDatabase()

# Do: depend on an abstraction
class OrderService:
    def __init__(self, db: DatabasePort):
        self.db = db

Code Smells

  • Long Methods – If a method exceeds 20-30 lines, it likely does too much. Extract smaller functions.
  • Large Classes – A class with many instance variables or methods is trying to do too much. Split it.
  • Duplicate Code – The same logic in multiple places. Extract into a shared function or module.
  • Magic Numbers – Raw numeric or string literals without explanation. Replace with named constants.
  • Deep Nesting – More than 2-3 levels of indentation. Use early returns, guard clauses, or extract methods.
  • Feature Envy – A method that uses more data from another class than its own. Move it to the class it envies.
  • God Class – One class that knows or does everything. Break it apart by responsibility.

Deep Nesting Example

Don’t:

def process(user):
    if user:
        if user.is_active:
            if user.has_permission:
                do_work(user)

Do:

def process(user):
    if not user or not user.is_active:
        return
    if not user.has_permission:
        return
    do_work(user)

Refactoring Tips

  • Extract Method – Pull a block of code into a well-named function.
  • Rename – If a name does not communicate intent, change it immediately. IDEs make this safe.
  • Replace Magic Numbers – Swap literals with named constants.
  • Simplify Conditionals – Use guard clauses, decompose complex boolean expressions into named variables or functions.
  • Remove Dead Code – Unreachable code, unused variables, and commented-out blocks should be deleted.

Simplify Conditionals Example

Don’t:

if (user.age >= 18 && user.hasId && !user.isBanned) {
  serve(user);
}

Do:

const isEligible = user.age >= 18 && user.hasId && !user.isBanned;
if (isEligible) serve(user);

Testing Practices

AAA Pattern

  • Arrange – Set up test data and preconditions.
  • Act – Execute the behavior under test.
  • Assert – Verify the outcome.
def test_withdraw_reduces_balance():
    account = Account(balance=100)     # Arrange
    account.withdraw(40)               # Act
    assert account.balance == 60       # Assert

One Assert Per Test

  • Each test should verify a single behavior. Multiple asserts per test obscure which behavior actually broke.

Descriptive Test Names

  • A test name should describe the scenario and expected outcome.

Don’t:

def test_1():

Do:

def test_withdraw_with_insufficient_funds_raises_error():

Test Edge Cases

  • Empty inputs, boundary values, null/None, very large inputs, and concurrent access.

F.I.R.S.T. Principles

  • Fast – Tests should run in milliseconds.
  • Independent – No test should depend on another test’s state.
  • Repeatable – Same result every time, in any environment.
  • Self-Validating – Pass or fail with no manual inspection.
  • Timely – Written at the same time as (or before) the production code.

General Principles

  • KISS (Keep It Simple, Stupid) – The simplest solution that works is usually the best. Avoid cleverness.
  • YAGNI (You Aren’t Gonna Need It) – Do not build features or abstractions until you actually need them.
  • DRY (Don’t Repeat Yourself) – Every piece of knowledge should have a single, authoritative representation.
  • Boy Scout Rule – Leave the code cleaner than you found it. Small improvements on every commit add up.
  • Principle of Least Surprise – Code should behave the way a reasonable reader would expect. Avoid hidden side effects and unconventional patterns.
  • Composition Over Inheritance – Favor combining small, focused objects over deep inheritance hierarchies. Inheritance creates tight coupling; composition keeps things flexible.