Understand what AI really is and why expectations matter.
Asking an AI to "build a signup form for the course" produces something that looks complete in five minutes. Two hours later, after a dozen small follow-up requests, it's a tangle of half-fixed issues nobody can quite explain. Why does the same goal, broken into ten small checkable steps instead, tend to actually finish working?
AI has no built-in mechanism to catch its own drift on a large, open-ended task — nothing forces a comparison between what was actually asked for and what's actually been produced until something visibly breaks, often long after the mistake was made. The fix isn't a smarter AI. It's the same fix software teams already adopted decades ago for the exact same problem in human teams: break work into small, explicitly specified pieces — a contract, stating exactly what goes in and exactly what comes out — verify each piece mechanically before accepting it — a test, not a feeling — and only then move to the next piece. This is Extreme Programming, applied to AI collaboration instead of a team of programmers. Not a new trick. An old, well-tested one, in a new setting.
AI tools are remarkably fluent, and that fluency is exactly what makes them risky to manage loosely. Confident, well-structured output reads as correct, and on a task large enough that no one is checking each individual piece along the way, that impression can survive long after the actual work has quietly gone wrong.
Software already solved this exact class of problem, for human teams, long before AI existed. Procedural programming organizes work into functions, each with one job and a defined input and output. Object-oriented programming organizes it into objects, each with a defined responsibility and interface. Both exist for the same underlying reason: to make a large task safely splittable into smaller pieces that can be checked independently.
Extreme Programming, a development approach built around exactly this kind of discipline, adds a layer on top: write the test before the work, so "done" is a mechanical pass or fail rather than a feeling; keep each release small enough to actually verify in full; and have a second set of eyes — a pair — catch what one person's own blind spots miss.
Applied to working with AI, a contract is a fixed, narrow specification for exactly what one step is allowed to touch — for a signup form, that might mean: this step only adds email validation, nothing else. A test is a check against that contract before the result is accepted — does the form actually reject a malformed email, yes or no — not a read-through that simply feels fine.
This lesson's running example — a course-signup form, built with AI assistance — continues through the rest of this course. Each lecture follows the same small project through a different way it can quietly go wrong, and how a specific habit catches it before it does.
A contract and a test, for one small step of the signup form
CONTRACT for one step: "Add email validation to the signup form"
INPUT allowed:
- the existing form's HTML
- the one rule: email field must contain an @ and a domain
OUTPUT required:
- validation logic for the email field only
- no other field touched
- no new fields added
TEST before accepting the output:
- submit the form with "not-an-email" in the email field
- confirm it is rejected
- submit the form with "name@example.com"
- confirm it is accepted
- confirm every other field still behaves exactly as before
RESULT:
- pass -> move to the next step
- fail -> reject and ask again, do not patch the output by hand
CONTRACT — This names exactly what one step of the signup-form project is allowed to do, before any work happens. It's the same role a function's defined input and output plays in software, or a class's defined interface — a boundary the work isn't supposed to cross.
INPUT allowed — Deliberately narrow. Limiting what's handed to the AI also limits what it's free to invent — a vague, wide-open request like "improve the form" is exactly what leads to fields being added, removed, or restyled that nobody actually asked for.
OUTPUT required — Exactly one capability, stated plainly: email validation, nothing else. Not "make the form better," which has no checkable shape at all.
TEST before accepting the output — This is the actual answer to the question at the top of this lesson. The test doesn't ask whether the form looks finished — it asks three specific, checkable questions, and only a clean pass on all three counts as done.
fail -> reject and ask again, do not patch the output by hand — Quietly hand-fixing a failed step and moving on repairs this one symptom while leaving the underlying request free to produce the same mistake again on the next field. Rejecting and re-running the contract is what actually prevents the same drift from accumulating across the whole form.
Small contracts make drift visible immediately, not two hours later
A signup form built one small, tested step at a time surfaces a problem with the email-validation step the moment it's added — not after a dozen more requests have already been layered on top of it, by which point untangling which step actually caused the issue takes far longer than fixing it would have.
A vague request quietly hands decisions to the AI
"Build a signup form" leaves field choices, validation behavior, and error-message wording entirely up to the AI, with no way afterward to tell which choices were actually intended and which were simply guessed. A narrow contract removes that ambiguity before a single line is produced.
A mechanical test catches what a quick glance hides
A signup form can look finished — clean layout, all the expected fields — while a malformed email still gets silently accepted, because nothing about the form's appearance reveals that the validation logic is missing or broken. Only actually testing the specific behavior shows that.
Small releases bound the cost of being wrong
One bad step in the signup form, caught immediately by its own test, costs a few minutes to fix. The same category of mistake, repeated unnoticed across the whole form's fields and validation, costs a far larger rework later, discovered at a far worse time — often after real visitors have already tried to sign up through it.
This discipline outlasts any specific AI tool
Extreme Programming has decades of track record across real software teams, entirely independent of which company or which model happens to be considered best this year. That's exactly why it's a more durable thing to actually learn than which specific AI tool currently ranks highest.
One big request, checked only at the end — avoid
"Build me a signup form for the JS course."
(reviewed only once, after everything was generated)
One small contract at a time, tested immediately — use this
Step 1: Add the name and email fields only.
-> test: do both fields exist and accept text?
Step 2: Add email validation.
-> test: is a malformed email actually rejected?
Step 3: Add the submit handler.
-> test: does a valid submission actually go through?
(continue, one verified step at a time)
Paste this lecture's contract into ChatGPT and ask it to write an equally narrow contract for the next step of your own project. Then do the part that matters most: write down, yourself, exactly what you would check to confirm its output before accepting it — the same way the TEST section above does. Don't accept the contract as good just because it reads as organized; that's the exact habit this lesson exists to break.
Pick one real feature you want AI help with. Before asking anything, write its contract: what input is allowed in, and exactly what output is required back out.
Write the test you'd use to mechanically check that output — before you send the request, not after you've read the response and decided how you feel about it.
Send the request, run your test against the actual result, and write down honestly whether it passed, or whether you would have accepted it anyway just because it looked finished.
Take one large request you'd normally send in a single shot — like "build a signup form" — and split it into three small contract-and-test steps instead. Compare how it feels to verify each step against reviewing one big combined result at the end.
Find one moment, in this course or elsewhere, where something that looked finished turned out not to be. Write down the specific test that would have caught it before you trusted it.
Sending one large, open-ended request — "build a signup form" — and reviewing only the finished result, instead of breaking the work into small contract-and-test steps along the way.
Treating something that looks complete as proof that it works, instead of running an actual, independent check against what was specifically required.
When a test fails, quietly patching the output by hand and moving on, instead of treating the failure as a signal that the request or the contract itself needs fixing.
Writing a contract with no explicit, exact output requirement, leaving the AI free to invent the shape of the result on its own.
Assuming this discipline is something special to AI, rather than recognizing it as the same fix procedural programming, object-oriented programming, and Extreme Programming specifically already applied to managing complexity in human-built systems, long before AI was part of the picture.
Break a large AI-assisted task into small, explicitly contracted steps, write a mechanical test for each one before accepting its output, and explain why this is the same discipline software teams already used to manage complexity, long before AI entered the picture at all.