Software Design Reference Master Designer · Evolution Trade-Offs

Lesson 0038 · Master Designer · Module 3

Short-Term Productivity vs Long-Term Maintainability

You may borrow from the future — every team does. The master's rule: record the loan. Debt that is invisible is never repaid; debt that is named gets a repayment plan.

Reading ≈ 9 minPractice ≈ 30 min

Mission tie-in: the final trade-off closes the Master Designer phase — maintainability is not the enemy of productivity; it is productivity, compounded. The Initiative Lead phase applies that judgment to a whole project.

Knowledge: the loan, the ledger, and the compounding

Ship the quick version now and the slow version later? That is a loan — you are borrowing future maintenance time to buy present shipping time. Loans are legitimate. What is never legitimate is a loan with no ledger entry: the debt exists, invisibly, and compounds through every future change (lesson 0035's flags, lesson 0037's skipped apparatus).

The principle behind the principle

The two forces are present throughput — value delivered this week, when it is worth most — and future change cost — the price of every edit this code will receive for the rest of its life. The unit of decision is the loan: one shortcut, one entry.

The interest is not a metaphor. The cost of a change is roughly the amount of code and coordination a person must hold in their head to make it safely; a shortcut enlarges that amount for every subsequent change that passes through the same place. And it compounds rather than accumulating, because two shortcuts in one area interact: you must now understand both, and the interaction between them, before you may touch either.

Which is exactly why the ledger, not the discipline, is the lesson. Unrecorded debt is not priced into estimates, so it does not appear as debt at all — it appears as the team getting slower for no visible reason. The signal degrades uniformly ("everything takes longer now"), the cause becomes unattributable, and the conversation turns into a personality argument about rigour instead of a cost conversation about a specific decision that a specific person made for a specific reason.

The mechanism has a sharp boundary, and it is the most useful sentence in the lesson: interest is only charged on code you touch. A gnarly module nobody has opened in two years has a large principal and an interest rate of zero. It is not debt; it is just old code, and repaying it converts real hours into no saved hours. Equally, code that will be deleted — a three-week campaign page, a migration script, a spike — has no future to borrow from, so investing in its structure is not prudence, it is waste.

The ledger entry has three lines:

  1. What was borrowed. The corner cut, named precisely: "the pricing logic lives in the endpoint, not the domain."
  2. What it costs. The interest: "every pricing change now edits a route handler and needs a manual regression check."
  3. The repayment. The trigger: "repay when the third pricing variant arrives."
# borrowed: a quick, coupled implementation
def endpoint(request: Request) -> Response:
    if request.json["tier"] == "gold":            # pricing logic in the route
        price = request.json["amount"] * 0.9
    ...
# ledger entry:
#   borrowed: pricing rules in the endpoint (not the domain)
#   interest: every pricing change edits routes + manual QA
#   repay when: third pricing variant arrives → extract PricingStrategy
#               (lesson 0004's shape, already visible)

And the compounding works the other way too: the clean code from Phase I isn't a luxury — it is future productivity purchased at small present cost. Ousterhout's point, now fully earned: a working system that's hard to change is not an asset, it's a liability that grows. The final summary of this course: maintainability is productivity over time, and every trade-off in this phase was the same trade in different clothing.

Worked: pricing one loan, and choosing which to repay

The pricing rule lives in the route handler because the first version needed to ship for a launch date. A year on, pricing changes arrive about three times a quarter. Each one now edits a route, cannot be unit-tested without spinning up the web layer, and needs a manual regression pass: roughly three hours. Extracted behind a strategy, the same change is about half an hour.

That is the interest, and it is arithmetic, not opinion: 2.5 hours × 12 changes a year = 30 hours a year, around €2,400 of loaded engineering time, charged annually and rising as variants multiply. The extraction costs about six hours once. Payback lands in roughly ten weeks, so this loan should be repaid at the next pricing change rather than scheduled, discussed, or added to a backlog where it will lose to a feature every time.

The part worth noticing is that taking the loan was correct. At the time there was one pricing rule, the launch date was real, and the extraction would have been speculative structure for a case that had not appeared (lesson 0030). What separates engineering from drift is not refusing to borrow; it is that the entry named a trigger — "the third variant" — and the third variant is the thing that arrived.

Now the harder decision. A team of six keeps a ledger with fourteen entries, and can repay perhaps two this quarter. The instinct is to rank by ugliness, which is why most debt lists are sorted by how much they annoy the person who wrote them. A lead ranks by rate instead, and gets it from version control: how many commits touched each file in the last ninety days. Two entries turn out to sit in files touched forty and thirty-one times; four sit in files touched once. The four are not repaid, and saying so out loud is the point — they are not debt this quarter, and pretending otherwise would spend a quarter of the team's repayment budget on hours that were never going to be charged.

The alternative a lead rejects is the tidy policy: "we dedicate 20% of every sprint to technical debt". It sounds disciplined and it is untargeted — it funds repayment of principal in code with no interest, and it lets genuinely expensive debt survive because it was not this sprint's turn. Repayment is triggered by the change that makes the debt expensive, and the trigger belongs next to the code, not in a document nobody opens at the moment of the edit.

† AI is the productivity multiplier that makes this trade acute: it generates the loan and the repayment, at speed. The AI workflow from lesson 0010 — structure first, review after — is the compounding protection.
Field notes · four loans, priced and dated
BorrowedThe interest it chargesRepay when
pricing logic in the route handlerEvery pricing change edits a route and needs manual QA.The third pricing variant arrives.
no test on the new adapterEvery refactor within reach of it needs a manual check.The first bug it lets through.
a copy-pasted service, “we’ll merge later”Two places to fix every shared bug, forever.The second shared bug.
# TODO: remove after launchOne extra branch in every future read of the function.Launch plus two weeks — put it in the calendar.
What people get wrong after reading this
"Technical debt means bad code."
Debt is a deliberate trade with a repayment plan. Code that is bad because nobody thought about it is not debt — it is mess, and calling it debt lends it a respectability that stops anyone asking who decided, and why.
"Mature teams pay down debt continuously."
Mature teams pay down the debt they are actually being charged for. A fixed 20% tithe spends real hours on principal in files nobody opens, while the expensive entry waits its turn. Repayment is triggered by the next change through that code, not by the calendar.
"The ledger is a backlog of tickets."
Debt tickets lose to features forever, because they are compared on the wrong axis. A ledger works when it is read at the moment of the change — a comment at the site, a named test, a line in the module's README — so the person about to pay the interest is the person who sees the entry.
"Clean code costs more up front."
Usually it costs about the same up front and much more to retrofit. What is genuinely expensive is not the first clean version; it is extracting a boundary after forty call sites have grown through the place where it should have been.

When this is the wrong move

Do not open a ledger entry for code with no future. Spikes, migration scripts, a promotional page with a fixed end date and anything you will delete before the next quarter charges no interest, and structuring it carefully is a cost with no matching return. Nor does the discipline pay before product-market fit: when the odds are good that the whole feature is deleted, the option value of shipping now exceeds the interest you would avoid.

And there is a repayment that is itself the wrong move: the one whose risk exceeds the interest. A rewrite of a critical path to remove an inconvenience you pay three hours a year for is not repayment; it is a new, larger loan taken to settle a small one — lesson 0037's irreversible class, entered voluntarily.

In the lab: the companion Phase 3 exercise closes the phase by having you write the debt ledger for the returns service you have been repairing — three entries with what was borrowed, what it charges, and the trigger — then rank them by how often version control says those files are touched.

Skill: name the loan

A quick implementation that cuts a corner is best described as:

Two loans sit in the ledger. Which is repaid first?

Clean, well-bounded code is best understood as:

Practice: close the trade-off phase

Write the ledger for your own codebase's three largest invisible loans — each with borrowed / interest / repay-when lines. Then carry the largest one into lesson 0039: it is part of the constraint and risk context for the next initiative you lead.

Reveal: the first three phases' closing argument

Three phases, one system of thinking: Core Designer taught you to see and repair structure; System Designer taught you to grow it safely; Master Designer taught you to choose under pressure. Every decision shared one aim: keep the future cheap. Phase IV now asks you to make that judgment visible and actionable for a whole cross-functional team.

Teach it back
  1. In two sentences, explain to a junior why ugly code that nobody opens is not technical debt.
  2. Find one loan in your own codebase, price its interest in hours per year from the real change frequency, and compare it with the hours repayment would take.
  3. Predict the one-year state if it stays unrecorded: what will the team say about their estimates, and what will they blame instead of the loan?

Your win

You can take productivity loans deliberately, record them in a three-line ledger, and repay them on schedule — the discipline that lets you ship fast and stay master of the system. Next, you lead the initiative that changes it.

Read and watch deeper

Your agent-teacher remains available for any lesson — re-explanations, code reviews, or designing your next feature together. Ask anything.