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:
- Build clean. Correct, clear, well-bounded code (everything Phase I taught). Performance is not yet a requirement — it's a guess.
- 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".
- Find the hot path. Usually 2–3% of the code does 95% of the work. Only those places trade maintainability for speed.
- 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.| The “optimization” | The number that would justify it | If there is no number |
|---|---|---|
| a hand-rolled cache | p95 latency, and the share of it spent in the cached call. | Delete it — the hidden state costs more than it saves. |
| a denormalized column | The query plan before and after, at production row counts. | Revert; a covering index is cheaper to maintain. |
| __slots__ on every class | Measured memory, at the object count you actually create. | Remove it; it constrains subclasses for nothing. |
| a batch size of 1000 | Throughput 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
- Designing Data-Intensive Applications, Kleppmann — ch. 1 (reliability, scalability) and ch. 12: what performance actually means and how to measure it.
- A Philosophy of Software Design, Ousterhout — ch. 20 "Designing for Performance": keep the clean design, then optimize the hot paths.
- Knuth, "Structured Programming with go to Statements" — the premature-optimization quote in its original context.
- Watch: ArjanCodes YouTube — search "profiling" or "optimization".
- Next module: lesson 0035 — Evolution Trade-Offs.
- Reference: Trade-offs — performance vs maintainability; Design Principles — deep modules.
Bring a cache or clever loop to your agent-teacher and hunt for its justifying number.