Writing Clean and Consistent Code

“Clean code” is shorthand for code that is easy to read, change, and trust. It has nothing to do with cleverness; the cleanest code is usually the most boring. Clean code follows predictable conventions for naming, formatting, and structure, so a reader can absorb what it does at a glance and spend cognitive effort on the interesting parts only.

For Python the convention starts with PEP 8. Four-space indentation, lowercase with underscores for variable and function names, CapWords for classes, UPPER_CASE for constants, and line length capped around 88–100 characters. Most modern formatters (black, ruff format) apply these rules for you; running them on save means you never argue about whitespace again.

Beyond formatting, clean code follows a handful of principles: small functions (under ~30 lines), self-documenting names (user_age_years beats a), minimal state, and a single level of abstraction per function. Each function answers one question; if it answers two, split it. Comments are for the why; the code itself tells the what.

Consistency is the other half of clean. Use the same idiom across a codebase: one style of error handling, one way to build paths, one logging setup. Linters and formatters make consistency automatic; code reviews catch the rest. Fast, automated checks are cheaper than the friction of doing them by hand.

Formatting and naming

Let the formatter decide whitespace. black is the most popular opinionated formatter; ruff format is compatible and very fast. Configure once in pyproject.toml; run on save or in CI.

Names are the cheapest documentation. days_until_expiry instead of d; load_config instead of do_it. Avoid generic names like data or util; they tell the reader nothing. Short names are fine for short scopes (loop indices, comprehensions).

Small functions and clear control flow

A function should do one thing at one level of abstraction. Nested if/for blocks more than two or three deep usually signal that the code wants to be split. Use early returns (“guard clauses”) to avoid the arrow of indentation.

Prefer pure functions where you can: inputs in, outputs out, no hidden state. They're easier to test and easier to reason about. Save side effects for narrowly-scoped wrappers at the edges of the system.

Style conventions and tooling.

ToolPurpose
PEP 8
style guide
Canonical Python style conventions.
PEP 20 (Zen)
philosophy
Guiding principles for Python code.
black
formatter
Opinionated auto-formatter.
ruff / ruff format
linter+formatter
Fast lint + PEP 8 checks + formatting.
PEP 257
style guide
Docstring conventions.
""" docstring """
convention
One-line summary at the top of every public function/class.
logging
module
Prefer over print in long-lived code.
PEP 484
spec
Type hints.

Writing Clean and Consistent Code code example

The script below shows a messy implementation being rewritten into a clean one piece by piece.

# Lesson: Writing Clean and Consistent Code
from dataclasses import dataclass


# BEFORE: messy, ambiguous, hard to test
def do_stuff(d):
    r = []
    for x in d:
        if x['a'] > 0:
            r.append(x['a']*2)
    return r


# AFTER: small, named, typed
@dataclass
class Sample:
    value: float


def doubled_positive(samples: list[Sample]) -> list[float]:
    """Return the doubled values of samples whose value is positive."""
    return [s.value * 2 for s in samples if s.value > 0]


data = [Sample(3), Sample(-1), Sample(5)]
print("clean  :", doubled_positive(data))
print("messy  :", do_stuff([{"a": 3}, {"a": -1}, {"a": 5}]))


# Guard clauses vs nested ifs
def describe(x: int | None) -> str:
    if x is None:
        return "missing"
    if x < 0:
        return "negative"
    if x == 0:
        return "zero"
    return "positive"


for v in (None, -1, 0, 5):
    print(f"describe({v!r:5}) -> {describe(v)}")


# Pure function vs side effects
def normalize(value: float, low: float, high: float) -> float:
    """Map value from [low, high] to [0, 1]. Raises on invalid range."""
    if high <= low:
        raise ValueError("high must be greater than low")
    return (value - low) / (high - low)


print("normalize:", normalize(7, 0, 10))

What changed between messy and clean:

1) Concrete types (dataclass Sample) replace opaque dicts with magic keys.
2) Function names describe outcomes; bodies are a single expression.
3) Guard clauses replace nested `if/else` staircases.
4) Docstrings document the contract in one line.

Rewrite an ugly one-liner for clarity.

# Before: hard to read
def f(lst): return [x for x in lst if x and x not in lst[lst.index(x)+1:]]

# After: two small steps, names that say what they mean
def unique_preserving_order(items: list) -> list:
    seen: set = set()
    out: list = []
    for item in items:
        if item and item not in seen:
            seen.add(item)
            out.append(item)
    return out

print(unique_preserving_order(["a", "b", "", "a", "c", "b"]))

Behavioral checks of the clean version.

from dataclasses import dataclass
@dataclass
class S: v: int
def doubled_positive(xs):
    return [x.v * 2 for x in xs if x.v > 0]
assert doubled_positive([S(1), S(-1), S(3)]) == [2, 6]
assert doubled_positive([]) == []

Running prints:

clean  : [6, 10]
messy  : [6, 10]
describe(None ) -> missing
describe(-1   ) -> negative
describe(0    ) -> zero
describe(5    ) -> positive
normalize: 0.7