Software Design Reference Master Designer · System Trade-Offs

Lesson 0034 · Master Designer · Module 2

Performance vs Maintainability

The fast version is usually the ugly version. The Master's move: keep the system clean, measure, and hide the few hot paths behind deep modules — so the ugliness has a small address.

Mission tie-in: the last System Trade-Off — where premature optimization and cargo-cult caching both get their verdicts.

Knowledge: clean until measured

The sequence that keeps both forces happy:

  1. Build clean. Correct, clear, well-bounded code (everything Phase I taught). Performance is not yet a requirement — it's a guess.
  2. Measure. A profiler, a load test, a real metric — not "this feels slow". The requirement is a number: "the checkout must return in 300ms p95".
  3. Find the hot path. Usually 2–3% of the code does 95% of the work. Only those places trade maintainability for speed.
  4. Hide the trade behind a deep module. The fast implementation lives behind the same clean interface — caches, indexes, and cleverness contained where no one else must read them.
# the interface stays clean:
class OrderRepository(Protocol):
    def get(self, order_id: str) -> Order: ...

# the hot path gets its speed, hidden:
class CachedOrderRepository:
    def __init__(self, inner: OrderRepository, cache: Cache) -> None:
        self._inner = inner
        self._cache = cache

    def get(self, order_id: str) -> Order:
        cached = self._cache.get(order_id)
        if cached is not None:
            return cached
        order = self._inner.get(order_id)
        self._cache.set(order_id, order)
        return order

The domain never learns about the cache (lesson 0023's ports at work). The fast-but-complex code has one address: the adapter. Knuth's verdict stands: premature optimization is the root of much evil — because it spends maintainability before the measurement exists. But the measured optimization, contained behind a boundary, spends a little maintainability for a lot of performance, deliberately.

The corollary for AI: AI will "optimize" on request — producing clever code with no measurement behind it. Require the number first; then let the cleverness be contained in an adapter.
Field notes · every optimization owes you a number
The “optimization”The number that would justify itIf there is no number
a hand-rolled cachep95 latency, and the share of it spent in the cached call.Delete it — the hidden state costs more than it saves.
a denormalized columnThe query plan before and after, at production row counts.Revert; a covering index is cheaper to maintain.
__slots__ on every classMeasured memory, at the object count you actually create.Remove it; it constrains subclasses for nothing.
a batch size of 1000Throughput measured at 100, 1 000 and 10 000.It is a guess wearing a constant’s clothes.

Skill: where does the speed live?

Before optimizing, you need:

The optimized implementation belongs:

"Premature optimization is evil" because it:

Practice on your own code

Find the "optimization" in your codebase — the cache, the micro-optimized loop, the clever data structure. Ask: what number justified it? If there is no number, delete it or write the number that would justify it. If there is a number, confirm the fast code is hidden behind a clean interface.

Reveal: a measured verdict

A report module had a hand-rolled LRU cache added "because reports were slow". The number: an instrumented run showed the bottleneck was a N+1 query, not the formatting the cache wrapped. The cache was deleted; the query was fixed in the repository adapter. Result: faster, and one less hidden state (lesson 0019's greps came out clean).

Your win

You can demand a number before any optimization, contain the measured hot paths behind deep modules, and audit existing "optimizations" for the evidence they never had.

Read and watch deeper

Bring a cache or clever loop to your agent-teacher and hunt for its justifying number.