How Software Development ChangedLesson 35 of 87

Contents

How Software Development Changed

Lesson 35 of 87. Sign in to keep your place.

Module 4 · Saying what you want

Design the seam, generate the filling

9 min read

Where the thinking should goCost to writeCost to change laterThe interfaceFour minutesthe signature, the types, thedocumented contractEvery caller, in lockstepand callers outside your repositorymay never change at allThe implementationClose to nothinggenerated against tests that camefrom the contractAn afternoonbehaviour is fixed, so the oldversion is the oracleImplementations became cheap and interfaces did not move at all, so the ratio between the two costswidened. Your attention should follow the ratio.
Implementations became cheap and interfaces did not move at all, so the ratio between the two costs widened. Your attention should follow the ratio.

Figure

The asymmetry that should govern your effort

An implementation can be replaced in an afternoon. An interface, once other code depends on it, is a commitment: every caller has to change, in lockstep, and if the callers are outside your repository you may never be able to change it at all.

That asymmetry always existed. It got sharper when implementations became cheap, because the ratio between the two costs widened. Your attention should follow the ratio.

The practical form: spend your thinking on the signature, the types and the contract; let the body be generated and verified.

Write the seam first, in full

Python
class RateProvider(Protocol):
    def rate(self, base: CurrencyCode, quote: CurrencyCode,
             at: datetime) -> Decimal:
        """Exchange rate from base to quote at the given instant.

        `at` must be timezone-aware. Returns the rate published most
        recently at or before `at`, which for weekends means Friday's.

        Raises RateUnavailable if no rate exists at or before `at`,
        which happens for dates before 2019-01-01 and for currency
        pairs we do not carry. Never returns a stale value silently.

        Must be safe to call concurrently.
        """

Nothing here is implementation. Every line is a decision:

  • The direction of the conversion is in the parameter names, so it cannot be got backwards silently.
  • at is required, which means somebody had to think about which instant, rather than defaulting to now.
  • Timezone-aware is stated, so a naive datetime is a documented error rather than a silent offset bug.
  • The weekend rule is stated, which is a real business decision that would otherwise be discovered in an audit.
  • The failure is named and typed rather than returning None and letting each caller invent a behaviour.
  • Thread safety is a contract, not an accident.

That docstring took four minutes and it specifies the implementation almost completely. Handing it over now is safe, because there is little left to guess.

Types are the machine-checked part of the contract

Everything expressible in the type system should be, because that part of the contract is enforced rather than merely written.

rate(base: CurrencyCode, quote: CurrencyCode) cannot be called with the arguments in the wrong order if CurrencyCode is a distinct type and the parameter names are used. -> Decimal prevents the float. A RateUnavailable exception type is discoverable in a way that a None return is not.

The docstring carries what the types cannot: the weekend rule, the staleness guarantee, the concurrency promise. Those are the parts that need a human, and they are the parts worth the four minutes.

Then the tests, then the body

The order matters and it is not ceremony.

  1. Signature and contract, as above.
  2. Tests derived from the contract, not from any implementation, because there is not one yet. Weekend behaviour. A date before 2019. An unknown pair. A naive datetime. Two threads.
  3. The body, generated, and checked against tests that already existed.

This is test-driven development, and its value went up rather than down. The reason is precise: TDD’s benefit was never the tests. It was that writing a test first forces you to decide the interface and the behaviour before you have an implementation to be anchored by. That is exactly the thinking that gets skipped when a plausible body appears in ninety seconds, and it is exactly the thinking that a generated test written afterwards cannot supply.

Design the seams so the mistakes are cheap

A useful habit when laying out a change: ask where you want the replaceable boundaries to be.

  • Pure functions at the core, effects at the edge. A pure function is trivially testable, trivially differentially testable, and safe to regenerate. A function that reaches the network in the middle of a calculation is none of those.
  • One place per external system. All calls to the payment provider behind one module. When the provider changes their API, and they will, you change one file and its tests, and everything else is untouched.
  • Narrow interfaces. A collaborator with three methods can be replaced, faked and reasoned about. One with twenty-two cannot, and it is the twenty-two-method interface that becomes impossible to modify.

Each of these makes a region of the system safe to regenerate wholesale, which is what you want: a clear boundary, a precise contract, verified behaviour, and no need for anybody to read the body carefully at all.

The one rule to keep

If you cannot write the signature and the contract yourself, you do not yet understand the problem well enough to accept an answer to it. Getting one anyway is how you end up owning a design you did not choose.

Every lesson here is open. Your progress is recorded once you pass the AI at Work exam.

Check your understanding

Why does writing the tests before the implementation matter more now than it did, given that a model can generate tests for existing code in seconds?

Pick the one you would defend. Your answer stays private.

Next lesson · 9 minBounding what a change is allowed to touch

Stuck on something?Ask Larissa, your AI tutor

How to cite: Addaly (2026) ‘Design the seam, generate the filling’, in How Software Development Changed. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/how-software-changed/interfaces-before-implementations (Accessed: 13 October 2026).