Writing Scalable and Maintainable Code

A script solves today's problem. A maintainable codebase keeps solving problems for months and years after its author has moved on. Writing code that scales in people-time as well as CPU-time is a combination of modest architecture, clear naming, strong test coverage, automated checks, and a few well-chosen boundaries inside the program.

The first structural decision is module layout. Group modules by feature, not by layer: users/ (models, service, api, tests) beats models/, services/, api/ each containing unrelated user, billing, and inventory code. Features grow and die together; a feature-first layout keeps the churn localized.

The second is explicit interfaces between components. A module exports a small, stable surface (functions, classes, types) via __all__ or by convention. Everything else is internal and can change freely. Clear boundaries make refactors safe and parallel work possible; muddled ones turn every change into an expedition.

The third is the 4-step “sustainability loop”: tests, types, formatter, linter, CI. Each is low-effort on its own; together they protect the code from drift. Add them once, run them on every commit, and the codebase keeps its shape automatically while features grow.

Architecture for growth

Keep functions short, modules cohesive, and dependencies flowing inward (clean architecture): business rules don't depend on frameworks. Log at the boundary; validate at the boundary; convert to domain types once.

Prefer composition over inheritance. Reach for inheritance only when the subclass is a strict is-a relation (and even then, favor mixins sparingly). @dataclass, Protocol, and plain functions are often all you need.

Processes that keep code healthy

Automated test suite running on every PR. Pre-commit hooks that run ruff, mypy, and the tests locally. Small, focused PRs. A changelog that documents breaking changes. These habits compound over time.

For scalability in the system sense — throughput, reliability, latency — keep stateless components (to scale horizontally), design idempotent operations (to retry safely), and use queues to decouple producers from consumers.

Maintenance habits and tools.

ToolPurpose
PEP 8
style guide
Canonical formatting.
ruff
linter+formatter
All-in-one Rust tool.
mypy
type checker
Static contract verification.
pre-commit
tool
Runs hooks on git commit.
pytest
test runner
De-facto test framework.
@dataclass
decorator
Simple structured types.
Protocol
class
Structural interfaces.
keepachangelog.com
spec
Good changelog format.

Writing Scalable and Maintainable Code code example

The script sketches a tiny feature-layered module (`users`) with a service function, a domain error, and a Protocol for the storage backend — the pattern scales to much larger projects.

# Lesson: Writing Scalable and Maintainable Code
from dataclasses import dataclass
from typing import Protocol


# ---- domain (no framework dependencies) ----
@dataclass(frozen=True)
class User:
    id: int
    name: str
    age: int


class UserNotFound(Exception):
    pass


# ---- port (interface) ----
class UserStore(Protocol):
    def get(self, uid: int) -> User: ...
    def all(self) -> list[User]: ...


# ---- adapters (concrete implementations) ----
class InMemoryUsers:
    def __init__(self):
        self._data: dict[int, User] = {}

    def add(self, u: User) -> None:
        self._data[u.id] = u

    def get(self, uid: int) -> User:
        if uid not in self._data:
            raise UserNotFound(uid)
        return self._data[uid]

    def all(self) -> list[User]:
        return list(self._data.values())


# ---- service (depends on the port, not on the adapter) ----
def active_adult_names(store: UserStore) -> list[str]:
    """Return sorted names of users who are adults."""
    return sorted(u.name for u in store.all() if u.age >= 18)


# --- wire up and run ---
store = InMemoryUsers()
for u in [User(1, "ana", 30), User(2, "ben", 15), User(3, "cai", 22)]:
    store.add(u)

print("adults:", active_adult_names(store))
try:
    store.get(999)
except UserNotFound as err:
    print("missing:", err)

Three layers, one file here:

1) Domain (User, UserNotFound) is plain Python — easy to test in isolation.
2) Protocol declares the storage contract without committing to an implementation.
3) Service depends on the Protocol; you can swap InMemoryUsers for a DB adapter.
4) Adding a new adapter never touches the domain or the service.

Add a SQL adapter for the same Protocol.

import sqlite3
class SqliteUsers:
    def __init__(self, conn): self.conn = conn
    def get(self, uid):
        row = self.conn.execute("SELECT id, name, age FROM users WHERE id=?", (uid,)).fetchone()
        if row is None:
            raise UserNotFound(uid)
        return User(*row)
    def all(self):
        rows = self.conn.execute("SELECT id, name, age FROM users").fetchall()
        return [User(*r) for r in rows]
# Same `active_adult_names(store)` works with SqliteUsers, thanks to Protocol.

Service behavior is independent of the storage backend.

s = InMemoryUsers()
assert active_adult_names(s) == []
s.add(User(1, "x", 10)); s.add(User(2, "y", 40))
assert active_adult_names(s) == ["y"]

Running prints:

adults: ['ana', 'cai']
missing: 999