RulesGENERALSTAGE-SPECIFICILLUSTRATIVE

Rules Determine Implementation

For each operation, ask what must always remain true afterwards. The cart's answers — quantity > 0, one entry per product, a total that is never negative, no unknown product, quantity within stock if inventory is enforced here — are not comments on the code; they are the reason the code has the lines it has.

The moveWorked exampleNext questions

The situation, the reflex, and why it stalls

Every lesson starts where being stuck starts: someone has a problem, and the first move that comes to mind feels like progress.

The question

You know the cart's operations. Before writing any of them, what must always remain true — and how does each answer turn into a line of code?

The situation

You have the list: add, remove, change quantity, view, total, clear. You open the editor to write addItem and stop, because you cannot see where the checks go, or which checks there should be. Every version you imagine has a different if at the top and you cannot say which is right.

The reflex

Write the happy path of addItem and add checks when something goes wrong. Push an item; it works; move on to removeItem. Validation feels like polish — something to sprinkle on once the functions exist.

Why it stalls

The happy path is written and looks finished, and the code has silently taken a position on every rule: quantity can be anything, the same product can appear twice, an unknown product goes straight in. Nobody decided those things; the absence of a line decided them.

What the reflex produces — and fails to produce
  • The happy path is written and looks finished, and the code has silently taken a position on every rule: quantity can be anything, the same product can appear twice, an unknown product goes straight in. Nobody decided those things; the absence of a line decided them.
  • The checks that do get added arrive from bug reports, one at a time, in whichever function the bug happened to surface in. changeQuantity rejects negatives; addItem does not; the total goes negative through the door nobody checked.
  • Asked "why is that if there?", the honest answer is "a test failed once". The line has no rule behind it, so nobody can say whether it belongs in the API layer, the database, or both — and the next refactor drops it.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

Precisely enough to apply it to a problem you have never seen — not a slogan.

  • Take each operation and ask one question of the state after it runs: what must be true now, no matter what the inputs were? Write the answers as sentences about the state, not about the code. "Every quantity is greater than zero." "There is one entry per product." "The total is never negative." "Every product id in the cart exists in the catalog."
  • Then ask the question the other way round: which operation could break this rule, and with which input? Quantity > 0 is threatened by addItem and changeQuantity, and by nothing else. One entry per product is threatened by addItem alone. That mapping says which function needs which check, before a line of it exists.
  • Now read the rule as an instruction. "Every quantity is greater than zero" means: a quantity that is not greater than zero must be refused. That sentence is one comparison and one rejection — if quantity <= 0: reject — and it is not something you invented; it is the rule, encoded. Every rule that survives this reading becomes a validation, a structural choice, or a rule you hand to another component.
  • Keep the rules that do not become lines. "The total is never negative" turns out to follow from positive quantities and non-negative prices, and the price check belongs to the catalog. A rule you hold but do not enforce here is still worth writing down, because it tells you what you are trusting somebody else for (Rules That Live Elsewhere).

What must always remain true

Ask the question of the cart's state, not of its code: after any operation, what is still true? The five answers below come from the concept record, and the shape of each is the same — a sentence about the state, then the check it implies, then the check in pseudocode. The first is written out in full because it is the shape every rule follows.

Notice what the rule does to the implementation. Nobody decided to write a comparison against zero; the rule "every quantity is greater than zero" *is* that comparison, read as an instruction. Code, here, is a precise encoding of a previously identified rule.

  • One logical entry per product — a find before the append, not an if.
  • The total is never negative — follows from positive quantities and non-negative prices; the price check belongs to the catalog.
  • An unknown product cannot be added — check the catalog before touching the items.
  • Quantity cannot exceed available stock — only if inventory is enforced here, which is a V5 decision, not a V0 fact.
The first rule, in full

rule Every quantity in the cart is greater than zero.

becomes validation Reject an add or a change whose quantity is ≤ 0; turn a change to exactly 0 into a removal, because a cart never holds a product with quantity 0.

becomes code
if quantity <= 0:
    reject "quantity must be positive"

Which operation threatens which rule

A rule is only interesting where something can break it. The grid answers "where does the check go?" before any function exists: a check belongs in exactly the operations that can violate the rule, and nowhere else. total and getItems read the state and can break nothing, so they carry no checks — an absence with a reason.

RuleaddItemchangeQuantityremoveItemtotal / getItemsEnforced by
quantity > 0quantity ≤ 0quantity < 0; 0 becomes removalthe cart's checks
one entry per productsame product added againfind-before-append; a map key; a unique constraint
total ≥ 0a negative pricethe catalog owns prices
product existsmade-up idabsent id is a no-opa product removed from the catalogcatalog check in addItem; the API repeats it
quantity ≤ stockover-stock add (V5)over-stock change (V5)inventory owns stock; the cart asks

Code that came from rules against code that did not

Both versions below add an item. One was written from the happy path and patched; the other was derived from the rules. They differ in exactly the lines the rules produce — and in whether anyone can say why each line exists.

Happy path, patched later
function addItem(cart, productId, quantity):
    if quantity == 0: return cart        -- added after a bug report
    append { productId, quantity } to cart.items
    return cart
Derived from the rules
function addItem(cart, productId, quantity = 1):
    if not catalog.has(productId): reject "unknown product"   -- product exists
    if quantity <= 0: reject "quantity must be positive"      -- quantity > 0
    item = find entry in cart.items with item.productId == productId
    if item exists: item.quantity = item.quantity + quantity  -- one entry per product
    else: append { productId, quantity } to cart.items
    return cart

The patched version silently accepts a negative quantity, swallows zero instead of refusing it, and creates a duplicate on the second add. Each missing line is a rule nobody wrote down, and each present line in the derived version can be traced back to one sentence about the state.

The implementation ladder

Concept, examples, pseudocode, code, tests, production — for the concept this lesson is about. Code is the fourth tab, not the first.

concept Shopping Cart beginner
Build it step by step →

Shopping Cart = A temporary collection of products the user intends to purchase, held between browsing and checkout.

Identity, ownership, lifetime
  • Does a cart have identity? Yes, weakly. Two carts with the same items are still two carts, because each belongs to someone and will become a different order. It needs an id once it leaves memory; in memory the variable is the identity.
  • Who owns it? A shopper — a logged-in user or an anonymous session. The owner is part of the state because "my cart" has to be findable again.
  • How long does it exist? From the first add until checkout or abandonment. Whether it survives a reload, a closed browser or a login is not a property of the concept; it is a persistence decision made later, and each answer changes where the cart lives.
  • Should it survive reload? Usually yes for a store, usually no for a demo. V1 in memory says no; V2 browser storage says yes on one device; V3 server storage says yes everywhere the user is logged in.
  • Should it survive login? Only if an anonymous cart and a logged-in cart are merged — a rule that does not exist in V1 and appears as a modification later.
State it must remember
  • itemscollection of CartItemkeepThe cart is its items; without them nothing else means anything.
  • items[].productIdidkeepThe reference to what is being bought. The catalog owns the product; the cart only points at it.
  • items[].quantityinteger > 0keepTwo laptops is one entry with quantity 2, not two entries — the rule "one entry per product" needs a quantity to hold.
  • owneruser id or session iddependsSo the cart can be found again by the person it belongs to.
  • items[].productNamestringderiveIt would be convenient to render the cart without a catalog lookup.
  • items[].pricemoneydependsThe total needs a price per item.
  • totalmoneydropEvery screen shows the total.
  • currencycodedependsPrices need a currency to be added.
  • createdAttimestampdropAbandoned carts might be expired or emailed about.
Operations
  • update Add item the updated cart
  • delete Remove item the updated cart
  • update Change quantity the updated cart
  • read View items the list of entries — product id and quantity — for rendering
  • domain Calculate total the sum of price × quantity over the entries
  • delete Clear cart the empty cart
Rules that must always hold
  • Every quantity is greater than zero.
  • One logical entry per product.
  • The total is never negative.
  • An unknown product cannot be added.
  • Quantity cannot exceed available stock — if inventory is enforced here.

How to do it

Most important first.

  • For every operation, write one line beginning "afterwards, it is always true that…" — and if you cannot finish the sentence, the operation is not understood yet (What Must Never Break).
  • Draw the grid operation × rule and mark which cells can break which rule. An empty column is a rule nothing threatens — probably derived. An empty row is an operation with no rule — probably a read.
  • Turn each threatened cell into a check, in words first: "reject when…", "look for an existing entry before…". Only then write the comparison (From Invariant to Validation).
  • Ask of each rule: is this ours to enforce, or are we relying on the catalog, the inventory service, or a database constraint? Write the answer next to the rule.
  • Rules found from examples are the reliable ones; a rule you cannot illustrate with a before and after is a guess (Rules Come From Examples).

Worked on a concrete problem

The move has to produce something. This is what it produced.

  • Cart, addItem. Afterwards it is always true that: every quantity > 0; one entry per product; every product id exists in the catalog. Threats: a zero or negative quantity; the same product added twice; a made-up id. Three sentences, three checks — and one of them, "one entry per product", is not an if at all but a find-before-append.
  • Cart, total. Afterwards it is always true that the total is ≥ 0. Threat: a negative price. But the cart never sets a price; it asks the catalog. The rule stays written down with the note "enforced by the catalog", and the cart's total has no check in it — deliberately, and with a reason someone can read.
  • Cart, changeQuantity. Afterwards: every quantity > 0. Threat: a change to 0 or below. The rule produces two lines, not one: negative is rejected, zero becomes a removal — because a cart never holds a product with quantity 0, and the rule is easier to keep than to check in every reader.

How you know it worked

What now exists that did not before, and what question you can now ask.

  • There is a short list of sentences about the cart's state, each one attached to the operations that could break it, and each one either a check, a structural choice, or a named dependency on another component.
  • You can point at any if in addItem and say which rule it encodes — and at any rule and say where it lives.
  • Adding a new operation starts with the question "which of these rules can it break?", not with a blank function.

The questions you can now ask

The field this whole domain exists for. After this lesson, these are the questions to put to an unfamiliar problem.

Next questions
  • ?After this operation runs, what must be true of the state no matter what the inputs were?
  • ?Which operations could break that, and with which input?
  • ?Is this rule a check, a structural choice, or something another component guarantees for me?
  • ?Which rule would I discover only by writing down an example — and have I written that example?

What can go wrong

How the move itself fails
  • Every property of the state becomes a rule. "The list is in insertion order" is a property of the array, not an invariant the shopper cares about; enforcing it produces checks that defend nothing. A rule earns its place by naming what would go wrong for someone if it broke.
  • The rules are written and then enforced in one giant validator at the front of every operation, including rules that operation cannot break. The grid exists so that each check sits where the threat is.
  • Rules are treated as fixed. "Quantity cannot exceed available stock" is conditional on inventory being enforced in the cart at all, which is a version decision; a rule written without its condition gets enforced in a version that does not have the component to enforce it against.
What the move costs
  • Writing rules before code delays the first running function, and on a concept you have implemented five times before the delay buys nothing you did not already know.
  • A rule written down is a rule someone will ask you to enforce, including ones you would rather leave to the database. The list creates obligations.
  • Rules found before examples tend to be the obvious ones; the surprising ones — one entry per product — usually arrive from an example, so the list is provisional until the examples exist.
Misreads
  • "So validation goes at the top of every function." Some rules are validations; some are structural — one entry per product is a find-before-append, and in a map representation it is the key. Reading every rule as an if misses the ones the data structure could hold for you.
  • "Rules are the same as requirements." A requirement says what the cart does; a rule says what stays true while it does it. "Add a product" is a requirement; "one entry per product" is the rule that makes the second add behave.
  • "Make illegal states unrepresentable" means every rule becomes a type. It is precise advice about *some* rules — the map makes duplicate entries unrepresentable — and useless about others: no type stops a quantity of zero at runtime without a check somewhere.

Where this applies

Problem-solving advice is stated as universal far more often than it is. These labels say what each method is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.

  • GENERALAny concept with state has rules, and the move — state sentence, threatening operations, check or structure or delegation — is the same for a cart, a rate limiter or a job queue; only the sentences differ.
  • STAGE-SPECIFICIn a V0 with one in-memory cart, every rule is enforced in the functions; the moment the cart is persisted or an API sits in front of it, the same rules are repeated at the boundary and some move to the database, so the list of rules stays and the map of where they live changes with every version.
  • ILLUSTRATIVEThe cart, its five rules and the quantities (Laptop × 2, a quantity of 0) are the concept record's invented example; no real store is described.

Where the depth lives

This domain asks the question and hands the answer off by name.