4.1 Print Shop — Q1 Synthesis

Aligned outcomes:

SLO 1

Describe the software development life-cycle.

You do not describe the lifecycle here, you run a project through it. Scoping the shop, designing the order before the behavior, building in an order that stays runnable, and handing it to a tester are §1.1's four phases doing real work on a program you own.

SLO 3

Describe, design, implement, and test structured programs using currently accepted methodology.

Design, implement and test is the whole section. Nine requirements each name the technique and the section that taught it, and the Problem Set asks you to defend your own design — trace your pricing, find the input your validation misses — rather than re-run a drill.

Learning Objectives

By the end of this section, you will be able to:

In this section, you will learn to:
  • run your own project through all four lifecycle phases from §1.1 — inception, elaboration, construction, and deployment — in order;
  • write a written scope for a program before writing any code, including what is explicitly out of scope;
  • design an order's data shape (properties and types) before designing any behavior;
  • build a multi-feature console program in an order that keeps it runnable at every step;
  • validate bad input with throw and try...catch, and persist data with localStorage and JSON;
  • hand your finished program to another person and learn from what breaks.

Chapters 1 through 3 taught you the pieces one at a time. This section spends them all on a single program that you design and build. It teaches no new JavaScript — every technique this project asks for was taught somewhere between §1.2 and §3.8, and each requirement below says exactly where.

This section is tagged SLO 1 and SLO 3, and the pairing is the whole point. SLO 3 is the building. SLO 1 is the lifecycle — and here you do not re-read what a framework activity is. You run your own project through the four phases §1.1 named: inception, elaboration, construction, deployment. The section is structured so that working through it in order is working through the lifecycle in order.

Why one big project instead of more drills

Small exercises check that you know a technique. A synthesis project checks something harder: that you can decide which technique a problem needs, and when. That decision-making is the actual job of programming.

4.1.1 What a Synthesis Project Is (and Isn't)

Definition 4.1.1: Synthesis Project

A synthesis project is a project that combines techniques already taught into one program you design yourself. It introduces no new language features; the exercise is deciding how the pieces fit together.

That definition carries two consequences, and both matter.

There is no starter code. Chapters 1 through 3 gave you a skeleton to fill in or a listing to trace. Here you start from an empty file. That sounds scarier than it is — every line you will write is a line you have already written in some smaller form. What's new is only the ordering and the connecting.

There is no single right answer. Two students can both satisfy every requirement below with different property names, different function splits, and different pricing structures. The design decisions are the exercise. When you compare your work with a classmate's, the question is never "whose is right?" but "what did each design make easy, and what did each make hard?"

What this project is not: it is not a copy-the-listing exercise, it is not a place to learn new syntax, and it is not a user interface project. Everything happens in the console, same as every editor in chapters 1 through 3.

The Project

Build a print shop order desk — a console program that takes print jobs, prices them, and remembers them.

A print shop takes orders for things like posters, business cards, T-shirts and banners. Each order has a product, a size, a quantity, and a customer name. The shop needs to price each order, show the day's orders back, total the takings, and still have those orders tomorrow.

The project is concrete on purpose. Unlike the Arcade Cabinet in §7.1, you do not invent the concept here — you invent the design: what an order looks like as an object, how pricing is decided, how the totals are computed, what happens when the data is bad.

The Lifecycle Spine

The four phases of §1.1 are the order of work for this whole section, and the subsections are labeled by phase:

  1. Inception — decide the scope. Which products does the shop sell? What is explicitly out of scope? Written down before any code. (§4.1.2)
  2. Elaboration — design the data before the behavior. What are an order's properties, and what type is each? Sketch it as pseudocode or a flowchart (§1.5) — the shapes are already taught. (§4.1.3)
  3. Construction — build in an order that keeps the program runnable at every step: price list, then price-one-order, then the array, then totals, then persistence. Persistence last. (§4.1.5)
  4. Deployment — hand it to somebody else without explaining it, and watch what breaks. (§4.1.6)

Working through this section top to bottom is running your project through the lifecycle. §1.1 named the phases and explained framework activities, umbrella activities, task sets, and prescriptive process models in depth, with figures. That material is there when you need it; this section builds on it instead of repeating it.

Ground Rules

Three boundaries keep this project honest, and they are worth stating up front:

Try It Now 4.1.1

Before reading further, write down two programs you have built or traced in chapters 1–3 that you expect to draw on for a print shop order desk, and name the specific technique each contributed. There is no single right answer, but your answer should name concrete sections (like "switch from §2.3"), not vague feelings like "loops."

Solution

There is no single right answer, but your answer should look something like this:

  • §3.5 — objects with named properties, because both the price list and each order are naturally objects.
  • §2.5 — throw and try...catch, because bad orders must not stop the day.
  • §3.8 — JSON.stringify / JSON.parse with localStorage, because the orders have to survive until tomorrow.
  • §3.7 — .map() and .filter(), because totals and questions like "which jobs are rush?" come from transforming the orders array.

If your list names at least one item from each of chapters 2 and 3, you are ready to plan the whole build. If some row above surprises you, re-read that section now rather than mid-project.

4.1.2 Inception: Scoping the Shop

Definition 4.1.2: Scope

The scope of a project is the written statement of what the program will do and — just as important — what it will explicitly not do.

Inception is the phase where you decide the scope, in writing, before opening an editor. For the print shop, answer these questions on paper first:

  1. Which products does the shop sell? Pick a small set — poster, business card, T-shirt, banner is plenty. Every product needs a base price on your price list.
  2. What sizes exist, and do they change the price? If posters come in small, medium, and large, say so now. An unrecognized size is an error case you will handle later.
  3. Who places orders, and what identifies them? A customer name is enough for this project.
  4. What is explicitly out of scope? Write this down too. Good candidates: no discounts, no tax, no inventory tracking, no dates beyond "today," no editing or deleting an order after it is placed.
Out-of-scope lists are load-bearing

Writing "no tax, no discounts" feels like admitting weakness, but it does the opposite: it stops you from redesigning the pricing halfway through construction. Professionals ship scope documents for exactly this reason.

Notice what inception does not include: no code, no pseudocode, no decisions about properties or functions. Those belong to elaboration. Inception answers "what are we building and what aren't we," nothing more.

Try It Now 4.1.2

Write a scope statement for your print shop: the product list with a base price for each, the sizes you support, and at least three things that are out of scope. There is no single right answer, but your answer should be specific enough that a stranger could tell whether a given feature belongs in your program or not.

Solution

There is no single right answer, but your answer should read something like:

> The shop sells posters ($8), business cards ($0.25), T-shirts ($12), and banners ($20). Sizes are small, medium, and large, and size changes the price by a multiplier. Each order records the product, size, quantity, and customer name. Out of scope: taxes, discounts, inventory, past-day orders, editing or deleting orders, and anything graphical — output is console text only.

Check yours against three tests: could a stranger add a new product without asking you a question? Could they tell whether "apply student discount" is in or out? Does every product you listed have a number attached? If yes to all three, your scope is done.

4.1.3 Elaboration: Designing the Order

Elaboration is where you design the data before the behavior. This ordering matters: if you know exactly what an order looks like, writing the pricing function is almost mechanical. If you don't, you'll invent the object shape while writing the function, and rewrite both twice.

Start with one order. Sketch its shape as pseudocode (§1.5 taught you the shapes):

an order has:
    product   -- text, must match a price-list name
    size      -- text, one of "small", "medium", "large"
    quantity  -- whole number, must be at least 1
    customer  -- text

Then the collections around it:

priceList: an object whose property names are products
           and whose values are base prices

orders:    an array of order objects -- today's orders,
           in the order they were taken

One order object, so you can see the shape concretely, looks like this:

const sampleOrder = {
  product: "poster",
  size: "large",
  quantity: 3,
  customer: "Dana"
};

This is a shape, not part of your final program — though keeping a sample order in your test code is genuinely useful.

For behavior, sketch the flow the same way: take an order, check it against the price list and quantity rules, multiply base price by size multiplier by quantity, push it onto the array. A flowchart from §1.5 is the right tool if the branching starts to feel tangled.

Why data-first design pays off

Every function you write in construction reads and writes these objects. Fix the shape now and the functions fall out of it. Change the shape later and every function changes with it.

Try It Now 4.1.3

Design the data for your shop: write the property list for an order with a type for each property, and describe the price list as an object. Then sketch, in pseudocode, the steps for pricing one order. There is no single right answer, but your answer should state a type for every property and should check the order before computing a price.

Solution

There is no single right answer, but your answer should cover:

  • Order properties with types: product (text), size (text), quantity (whole number ≥ 1), customer (text).
  • Price list: an object like { poster: 8, "business card": 0.25, tshirt: 12, banner: 20 }.
  • Pseudocode for pricing one order:
  to price one order:
      if quantity is less than 1, signal an error
      if product is not on the price list, signal an error
      find the size multiplier using the size name
      return base price × multiplier × quantity

If your pseudocode computes a price before checking validity, reorder it — validation always comes first. If any property lacks a type, go back and name it; "some kind of value" is not a design.

4.1.4 The Requirements

What the Finished Program Must Do

Here is the full requirements list. Each row names where the technique was taught, so if a row feels shaky, that's your re-reading list — before you build, not during.

Table 4.1.1 — Print shop requirements and where each was taught.
RequirementTaught in
Hold a price list — products and base prices — as an object with named properties.§3.5
Represent one order as an object, and the day's orders as an array of those objects.§3.3, §3.5
Decide a size multiplier with a switch, with a default for an unrecognized size.§2.3
Price one order in a function that takes parameters and returns a value — never one that prints and returns nothing.§3.1, §3.2
Validate an order before pricing; throw when quantity is zero/negative or the product isn't on the price list; catch so one bad order doesn't stop the day.§2.5
Produce the day's total from the orders array using .map() plus a loop or reduction — not a running total kept in a variable the whole way through.§3.7, §2.2
Filter or slice the orders to answer one question — rush jobs, biggest three, one customer's orders.§3.7
Save the day's orders with JSON.stringify and localStorage.setItem; load with getItem and JSON.parse, handling parse failure with try...catch.§3.8
Use const by default, name things so a stranger can read them, comment the why not the what.§1.2, §1.3

Two stretch requirements, optional:

Definition 4.1.3: Requirement

A requirement is a specific, checkable statement of something the finished program must do. A good requirement can be tested: either the program does it or it doesn't.

Read Table 4.1.1 with that test in mind. "Price one order in a function that takes parameters and returns a value" is checkable — you can point at the function and ask whether it returns. "Make the program nice" is not a requirement, because nobody can say whether it's met.

Try It Now 4.1.4

Pick any three rows of Table 4.1.1 and write, for each, the exact check you would run to confirm your finished program meets it. There is no single right answer, but your answer should produce a yes/no result — not "it seems to work."

Solution

There is no single right answer, but your checks should look like:

  • Price list: open the file, find one object literal whose property names are exactly your scoped products and whose values are all numbers. Yes/no.
  • Validation: call the pricing function with quantity 0. It must throw. Call it with a product name not on the list. It must throw. Both yes/no.
  • Persistence: place two orders, reload the page (or rerun), load from storage, and print the count. It must print 2. Yes/no.

Notice each check names an input, an action, and an expected observable result. If your check can't fail, it isn't checking anything.

4.1.5 Construction: A Build Order That Stays Runnable

Definition 4.1.4: Build Order

A build order is the sequence in which you construct a program's pieces, chosen so the program runs — even if it does very little — after every step.

Construction is where most of your hours go, and the build order decides whether those hours are calm or chaotic. Build in this order:

  1. The price list. One object literal. Run it. Print it. It works.
  2. Price-one-order. A function taking (order, priceList) that returns a number. Test it with a valid order and watch it return the right price. Test it with quantity 0 and watch it throw.
  3. The switch for size multipliers. Add it inside the pricing function, with a default arm that throws on an unrecognized size.
  4. The orders array. Push priced orders onto it. Print the array after each push.
  5. Totals and questions. Use .map() over the array to get prices, then total them with a loop. Use .filter() or .slice() for "rush jobs" or "biggest three."
  6. Persistence, last. Save with JSON.stringify + localStorage.setItem; load with getItem + JSON.parse inside a try...catch.

Why is persistence last? Because everything before it works on data already in memory. Persistence adds a second copy of your data and a failure mode (corrupted stored text) that has nothing to do with pricing. Build it last and, when it misbehaves, you know the bug is in the saving/loading layer — not hiding somewhere in six other features.

Two shapes to aim at, so you can see what "right" looks like without copying a whole solution. First, the pricing function's skeleton:

function priceOrder(order, priceList) {
  // why: a bad order must fail loudly, not silently price as 0
  if (order.quantity <= 0) {
    throw "quantity must be at least 1";
  }
  if (!(order.product in priceList)) {
    throw "unknown product: " + order.product;
  }
  let multiplier = 1;
  switch (order.size) {
    case "small":
      multiplier = 1;
      break;
    case "large":
      multiplier = 1.5;
      break;
    default:
      throw "unknown size: " + order.size;
  }
  return priceList[order.product] * multiplier * order.quantity;
}

Second, the day's total via .map() plus a loop — note there is no running total accumulating across the whole program:

const prices = todaysOrders.map(function (order) {
  return priceOrder(order, priceList);
});
let total = 0;
for (let i = 0; i < prices.length; i = i + 1) {
  total = total + prices[i];
}

And the persistence pair, which is the entire technique from §3.8:

// why: storage holds text, so we serialize before saving
localStorage.setItem("printshop-orders", JSON.stringify(todaysOrders));

let loaded = [];
try {
  loaded = JSON.parse(localStorage.getItem("printshop-orders"));
} catch (error) {
  loaded = []; // why: corrupt text shouldn't kill the day
}
Why "runnable at every step" matters

A program that runs after every step gives you a working fallback at all times. A program you build in one giant push gives you forty errors and no idea which of the forty lines caused the first one.

These are shapes, not the finished program. Your version will differ — different multiplier values, different error messages, maybe a helper for the switch. That's the point.

Definition 4.1.5: Validation

Validation is checking input against the rules before using it — here, confirming the quantity is at least 1 and the product is on the price list — and signaling failure with throw when a rule is broken.

Definition 4.1.6: Persistence

Persistence is making data outlive the program run, by saving it to localStorage as JSON text and loading it back with JSON.parse.

Try It Now 4.1.5

Trace priceOrder by hand for the sample order { product: "poster", size: "large", quantity: 3 } with a price list where posters cost $8, then trace it again for { product: "mug", size: "medium", quantity: 2 }. There is no single right answer, but your traces should show the value of every variable at each step and state exactly where the second call stops.

Solution

Trace 1: order.quantity is 3, so the first check passes. "poster" in priceList is true, so the second check passes. The switch matches "large", so multiplier becomes 1.5. The return computes \(8 \times 1.5 \times 3 = 36\). The function returns 36.

Trace 2: order.quantity is 2, so the first check passes. But "mug" in priceList is false — mugs aren't on the list. Execution jumps to the second throw and the function stops right there, throwing "unknown product: mug". No multiplier is computed and no price is returned. Whoever called it catches the throw (or, if nobody does, the program stops — which is why the caller wraps calls in try...catch).

If your first trace got a different number, check the order of operations: base price, then multiplier, then quantity. If your second trace returned a price instead of stopping, re-read §2.5 — a missing product must throw, never silently price as zero.

4.1.6 Deployment: Handing It Over

Deployment for this project is refreshingly low-tech: give your program to somebody else — a classmate, a friend, anyone — without explaining it, and watch what they do.

No explanations is the rule that makes deployment worth doing. The moment you say "oh, type the size in lowercase," you've patched the program with your voice instead of your code. Whatever they get wrong is information:

Keep notes on everything that surprised them. Each surprise is either a bug to fix or a scope line you wish you'd written in inception. Either way, it goes back through the lifecycle: fix it in construction, and if it reveals a misunderstanding about what the shop does, amend the scope document first.

The person who breaks your program is doing you a favor

You cannot see your own assumptions — you wrote them. A fresh user trips over every one of them in the first five minutes, for free.

Try It Now 4.1.6

Before handing your program over, write a three-line handover script: the minimum someone needs to start using it, with no explanation of how it works. Then predict the first thing they will get wrong. There is no single right answer, but your prediction should name a specific input, not "they might make a mistake."

Solution

There is no single right answer, but your script should read like:

> 1. Open the file in the editor and press Run. > 2. Answer the questions it asks you, one at a time. > 3. To see yesterday's orders, run it again tomorrow.

And a prediction should be concrete: "they will type Large with a capital L, and my switch will hit default and throw." If that's your prediction, you have a choice to make before deployment — normalize the input with toLowerCase() (§2.2 string methods) or accept that the error message must explain the accepted spellings. Either is defensible; being surprised by it after handover is not.

Problem Set 4.1

These problems test whether you can reason about your own design. Answer them about your print shop program.

4.1.1 Trace a complete price calculation for an order of your choosing through your pricing function: state the order object, the relevant price-list entry, the size multiplier your switch assigns, and the arithmetic of the final return value.

Solution

Step 1 — Choose an order and state it: I pick a banner order for customer Priya:

const order = { product: "banner", size: "large", quantity: 2, customer: "Priya" };

Step 2 — Read the price-list entry: My price list has banner: 20, so the base price is $20 per unit.

Step 3 — Run validation: order.quantity is 2, which is at least 1, so the first check passes. "banner" in priceList is true, so the second check passes.

Step 4 — Assign the size multiplier: The switch on order.size matches case "large", so multiplier becomes 1.5.

Step 5 — Compute the return value: The function returns base price × multiplier × quantity:

$$20 \times 1.5 \times 2 = 60$$

Answer: The pricing function returns 60 — Priya's two large banners cost $60 in total.

4.1.2 Find one input your validation does not catch but plausibly should. State the input, explain what your program currently does with it, and say whether fixing it belongs in scope or on the out-of-scope list — with a reason.

Solution

Step 1 — State the uncaught input: My validation checks quantity ≥ 1 and that the product is on the price list, but it does not catch a non-numeric quantity such as "three" (a string) or a fractional quantity like 2.5.

Step 2 — Explain what happens now: With "three", the comparison "three" <= 0 is false, so validation passes; then priceList["poster"] 1.5 "three" produces NaN, and my day's total silently becomes NaN. With 2.5, I get a plausible-looking but wrong price (posters: \(8 \times 1 \times 2.5 = 20\)) with no error at all.

Step 3 — Decide scope: Fixing this belongs in scope, not on the out-of-scope list. The reason: a silent NaN total corrupts the shop's takings, which is core to what the program exists to do — unlike, say, tax handling, which changes the business rules. A one-line check (typeof order.quantity !== "number" or quantity is not a whole number) is cheap insurance inside existing validation, so it doesn't expand the design either.

Answer: Non-numeric or fractional quantities slip through today and produce NaN or subtly wrong prices; fixing them belongs in scope because they corrupt the program's central output, and the fix is a small addition to existing validation rather than a new feature.

4.1.3 Explain, in your own words, why totaling the day's takings with .map() plus a loop is better than keeping a running-total variable updated every time an order is taken. Name at least two concrete ways the running-total approach can go wrong.

Solution

Step 1 — State the better approach: Computing prices with .map() over the orders array gives me a fresh array of numbers derived entirely from the stored data, and totaling that array with a loop means the total is always recomputable from the orders alone.

Step 2 — Failure mode 1 of the running-total variable: If an order throws during validation mid-day (say, a bad size), the running total may already include earlier orders while the code path aborts — leaving the variable holding a partial total that no longer matches the array. Recomputing from .map() can never disagree with the data.

Step 3 — Failure mode 2: A second place where an order is added (a test push, a loaded-from-storage path, a retry after a typo) can update the orders array without updating the running total — or update the total twice if the same order is priced again after a correction. Two sources of truth drift apart silently.

Step 4 — Bonus failure mode: Persistence interacts badly too: if saved orders are reloaded into the array tomorrow, does the running total reset? Nobody remembers to ask, whereas a derived total simply reflects whatever is in the array.

Answer: .map() plus a loop keeps one source of truth — the orders array — so the total is always derivable. A running-total variable can go wrong when an aborted/failed order leaves it partially updated, and when any code path adds or reprices an order without updating it, making the total silently disagree with the data.

4.1.4 Your build order puts persistence last. Give a specific bug that could appear in the persistence step, and explain why building persistence first would have made that bug harder to locate.

Solution

Step 1 — Name a specific persistence bug: A realistic bug: I save with JSON.stringify(todaysOrders) but load with JSON.parse(localStorage.getItem("printshop-orders")) where the key name is misspelled ("printshop-order"), so getItem returns null and JSON.parse(null) yields null — and then loaded.map(...) crashes with "cannot read properties of null."

Step 2 — Why building persistence first makes this harder: If persistence were step 1, the crash would appear in a program that also contains pricing, the switch, totals, and filtering — all written afterward, all suspect. Every later feature touches the orders array, so a null-array crash could plausibly originate in any of them, and I would spend time checking pricing logic that is actually fine.

Step 3 — Why last makes it easy: Built last, everything before it demonstrably works on in-memory data. When the null crash appears immediately after adding only the persistence lines, the suspect set is exactly those few lines — and I find the misspelled key in minutes.

Answer: Example bug: a mismatched storage key makes getItem return null, crashing later .map() calls. Building persistence first would bury that crash among every subsequent feature's possible causes; building it last confines the suspects to the newly added save/load code.

4.1.5 A classmate's program stores each order as a separate localStorage key (order1, order2, …) instead of one JSON array. Compare the two designs: what does theirs make easier, what does it make harder, and which would you rather debug?

Solution

Step 1 — What their design makes easier: Per-order keys make single-order operations easy: deleting or inspecting one order is just localStorage.removeItem("order3") or reading one key, with no need to parse and rewrite a whole array. It also avoids one corrupt blob destroying everything.

Step 2 — What it makes harder: Loading requires knowing how many keys exist — JavaScript's localStorage offers no taught way to enumerate keys, so the program must store a separate count (which itself can drift out of sync) or probe keys until one is missing. Ordering is also lost unless keys encode sequence, and saving the whole day means N writes instead of one atomic write — a crash midway leaves half the day saved.

Step 3 — Which I'd rather debug: I'd rather debug mine. One key holding one JSON array fails in one obvious way (parse error caught by my try...catch, falling back to an empty day), whereas their count-drift bug shows up as "some orders vanished" with no error message pointing anywhere.

Answer: Their design eases per-order deletion and isolates corruption, but complicates loading (no easy key enumeration), loses ordering, and risks partial saves. I prefer debugging the single-array design because its failure mode is loud and localized, while theirs fails silently through count drift.

4.1.6 During deployment, your tester typed banner with two n's and the program threw "unknown product: bannner". Walk through what happened, from keystroke to error message, naming every piece of your program involved — and decide whether the fix is better validation, a better error message, or neither.

Solution

Step 1 — Keystroke to string: The tester presses b-a-n-n-n-e-r and Enter. The input routine reads the raw text "bannner" — three n's — as a string, with no cleanup step in my program that normalizes or checks spelling against the product list before use.

Step 2 — Into the order object: That string becomes the product property of the order object passed to my pricing function.

Step 3 — Validation runs: My validation checks "bannner" in priceList. The price list contains "banner", not "bannner", so membership is false — exactly as designed. This is the validation layer doing its job: rejecting an unknown product.

Step 4 — The throw: Because membership failed, execution reaches the throw "unknown product: " + order.product line, string concatenation builds "unknown product: bannner", and the enclosing try...catch catches it and prints the message instead of letting the program die.

Step 5 — Verdict on the fix: The system behaved correctly — bad input was rejected loudly. The fix is a better error message, not more validation: something like "unknown product 'bannner'. Valid products: poster, business card, t-shirt, banner" lets the tester self-correct. Optionally, normalizing case with toLowerCase() helps, but no reasonable validation should guess that three n's mean two.

Answer: The keystrokes became the string "bannner", stored as the order's product; the in-check against the price list failed; the throw produced "unknown product: bannner"; and the catch displayed it. The right fix is a better error message listing valid products — validation already worked correctly.

4.1.7 Suppose the shop adds a fifth product after deployment. List every place in your program that must change, in order, and say which lifecycle phase each change belongs to.

Solution

Step 1 — Decide what a fifth product actually is (inception): Selling a new product is a business decision before it is a code change. The scope statement said which products the shop sells, so it is the scope statement that changes first — add the product, and say whether it prices the same way the other four do.

Step 2 — Ask whether the data shape still fits (elaboration): If the new product is priced per unit like the others, nothing about an order's shape changes and there is no elaboration work at all. If it is priced differently — a banner charged by the square foot rather than by the piece — then an order needs a new property and the pricing function needs a new branch, and that is a design change, not a typo fix.

Step 3 — List the code that changes (construction): In a good design there is exactly one place: the price-list object gains one property.

const priceList = { poster: 8, businessCard: 3, tShirt: 12, banner: 20, sticker: 2 };

Everything downstream reads the price list instead of naming products itself, so it all keeps working untouched: validation asks whether the product is in the price list, the size switch branches on size and never on product, the pricing function looks the base price up, and the totals, the filter and the save/load code never mention a product name at all.

Step 4 — Re-test the handover (deployment): Take one order for the new product end to end — price it, total it, save it, reload it — and check the error message for an unknown product still reads correctly now that the valid list is longer.

Step 5 — What a bad design would have cost: If products had been hard-coded — a switch on product name inside the pricing function, another list inside validation, a third inside the receipt formatter — the same one-line business change would mean edits in three places, and missing any one of them produces a product that can be ordered but not priced. That is the difference the price-list object buys you.

Answer: In a well-designed program the only change is one new property on the price list (construction), because validation, pricing, totals and persistence all read that object rather than naming products themselves. Adding the product to the scope statement is inception; deciding whether it prices like the existing four is elaboration; and re-running a single order end to end is deployment. If the change touches more than one place, that is evidence the product list was duplicated somewhere it should not have been.

4.1.8 Write the scope amendment you would need if the shop owner asked for "yesterday's orders too, not just today's." State what stays out of scope and what new requirement you would add to Table 4.1.1.

Solution

Step 1 — Write the amendment: Scope statement addition: "The shop retains orders from previous days. Each order records the day it was placed. The desk can show and total any stored day's orders." Out-of-scope stays: taxes, discounts, inventory, editing/deleting past orders, graphical output — and I explicitly keep out anything beyond simple per-day viewing (no weekly reports, no trends).

Step 2 — New requirement for Table 4.1.1: "Tag each order with its day at creation, and provide a function that returns the orders and total for a specified day, using .filter() on the orders array. Taught in §3.7."

Step 3 — What stays out of scope and why: Editing or deleting yesterday's orders remains out of scope — the owner asked to see past days, not change them, and read-only history keeps the persistence format unchanged. Aggregated reporting across days also stays out until asked for specifically.

Answer: Amendment: orders gain a day property and the desk gains a view-and-total-by-day feature backed by .filter(); the new Table 4.1.1 row covers tagging orders with their day and filtering by day; editing/deleting historical orders and multi-day reporting remain explicitly out of scope.

Key Terms

synthesis project — a project combining already-taught techniques into one program you design yourself; no new language features.

scope — the written statement of what a program will and will not do.

requirement — a specific, checkable statement of something the finished program must do.

build order — the construction sequence chosen so the program runs after every step.

validation — checking input against the rules before using it, throwing on failure.

persistence — making data outlive the program run via localStorage and JSON.