Small Verified Changes
Before you start
You need: prompting (Article 02) and Git (Track 16).
Time: about 45 minutes, at the keyboard.
Learning objective
Break work into changes small enough to verify, and verify each one before moving on.
Topics
- Why large generated changes fail
- Sizing a change
- The loop: ask, review, test, commit
- Reviewing generated code
- Verifying behaviour, not compilation
- Committing AI-assisted work
- When to reject and write it yourself
- Multi-file changes
Why large generated changes fail
Ask for a complete feature and you get 400 lines across six files. It compiles. It looks right.
| Problem | Consequence |
|---|---|
| Too large to review properly | You approve what you have skimmed |
| Several assumptions embedded | Any of them may be wrong |
| One bug is hard to locate | The whole change is suspect |
| Reverting loses everything | Including the parts that were correct |
| You did not design it | You cannot maintain it |
This is the pull request size problem, with the author absent. A 400-line diff gets skimmed by a reviewer who at least talked to the author. A 400-line generated diff has no author to ask.
The failure mode is specific: the code works for the case you tested and is wrong for a case you did not think about — because the model did not know to think about it either.
Sizing a change
| Change | Size | Verifiable? |
|---|---|---|
| One repository method | 15 lines | Yes — run it against real data |
| One endpoint using that method | 20 lines | Yes — call it in Postman |
| One validation rule | 5 lines | Yes — send a bad request |
| One test | 15 lines | Yes — it fails, then passes |
| "The whole fee module" | 400 lines | No |
A good change is one you can hold in your head and check in a few minutes.
Building fee defaulters, in order:
1. SQL query in SSMS → verify: correct rows against known data
2. Repository method → verify: unit test, and a manual call
3. Service method → verify: business rules, edge cases
4. Controller endpoint → verify: Postman, including the 401 case
5. Frontend call → verify: the network response
6. Rendering → verify: the page against the response
Six verified steps beat one unverified feature, and when step 3 is wrong you know exactly where the fault is.
Start at the database and work outwards. Each layer is then built on something already proved.
The loop
1. Decide the next small change
2. Ask, with context and constraints
3. READ the output, line by line
4. Test it — run it, not just compile it
5. Commit
6. Repeat
Step 3 is where the value is, and it is the step people skip. Code you have not read is code you cannot maintain and cannot defend in review.
Step 5 matters more than it looks. Committing each verified change means a bad next change is one git restore away — instead of unpicking three merged changes to find which broke the build.
Reviewing generated code
Read in this order:
1. Does it do what I asked? Not something adjacent, not something more.
2. Does it follow the project's rules?
-- Check every generated query
WHERE SchoolId = @SchoolId -- from the claim?
AND IsDeleted = 0 -- soft delete?
// Check every grading chain
if (result.IsAbsent) { ... } // first?
// Check every money type
decimal amount // not double
3. Does it handle the edge cases? Null, empty, zero, absent, duplicate, the maximum.
4. Is anything invented? Methods, packages, configuration keys.
5. Is it more than I need? Generated code often adds caching, retries, logging and interfaces you did not ask for.
6. Does it match the house style?
Specific things to check that generated .NET code commonly gets wrong:
| Check | Common failure |
|---|---|
SchoolId source | Taken from a method parameter fed by a query string |
| Soft delete | Filter omitted |
| Money type | double or float |
| Absent handling | Compared before the absent check |
| SQL parameters | String interpolation into the query |
async | Missing await, or .Result |
| Disposal | Connection without using |
| Exceptions | catch (Exception) returning a default |
Six of those eight produce no error message. That is why reading beats running.
Verifying behaviour
Compiling is not working.
// Compiles. Wrong.
public string GetResult(ExamResult result, Subject subject)
{
if (result.MarksObtained >= subject.PassingMarks) { return "Pass"; }
if (result.IsAbsent) { return "Absent"; }
return "Fail";
}
Verify with actual cases:
| Case | Expected |
|---|---|
| Marks 85, passing 35 | Pass |
| Marks 20, passing 35 | Fail |
| Absent, marks null | Absent |
| Marks exactly 35 | Pass |
| Passing marks 0 | Pass |
Write the cases before you look at the code. Reading the implementation first anchors you to what it does rather than what it should do.
[Fact]
public void GetResult_AbsentStudent_ReturnsAbsent()
{
ExamResult result = new ExamResult { IsAbsent = true, MarksObtained = null };
Subject subject = new Subject { PassingMarks = 35 };
string outcome = _service.GetResult(result, subject);
Assert.Equal("Absent", outcome);
}
A test written from the requirement catches the wrong-order bug immediately. A test written from the code does not.
Ask for the tests separately, after reviewing the implementation. Generated together, tests tend to assert whatever the code does — including the bug.
Committing AI-assisted work
Commit as you would any other change: small, with a message explaining why.
Add fee defaulter query to FeeRepository
Returns accounts where the balance is above zero and the due date has
passed, for one school. Filters by SchoolId from the claim and excludes
soft-deleted students.
Written with Claude, reviewed and tested against the seed data.
Whether to mention the tool is a team convention — some teams require it, some consider it irrelevant. Follow the team's.
What is not optional: you are responsible for the code. "The AI wrote it" is not an explanation in review, an incident, or an interview. Committing it makes it yours.
Never commit code you have not read. That rule alone prevents most of the ways this goes wrong.
When to reject and write it yourself
| Situation | Do it yourself |
|---|---|
| Third attempt still wrong | Yes — the prompt is not the problem |
| Business logic you understand better | Yes |
| Security-critical code | Yes, or review it as if hostile |
| A small change you can type faster | Yes |
| Code you would not be able to explain | Yes |
| Boilerplate DTOs and mappers | No — generate them |
Three failed attempts means the task is under-specified or genuinely hard. More prompting rarely fixes either; writing it yourself usually does, and takes less time than the fourth attempt.
Correcting generated code you do not understand is the worst position to be in. You are debugging someone else's assumptions without being able to ask them anything.
Multi-file changes
Sometimes a change genuinely spans files — an entity, a repository, a controller and a test.
Ask for a plan first:
I need to add a "transfer student to another class" feature.
Don't write code yet. List the files that need to change and what
changes in each, so I can check the approach.
Then implement one file at a time, verifying each.
Reviewing a plan is far cheaper than reviewing 400 lines, and a wrong approach is obvious in a plan and invisible in code that compiles.
For tools that edit the repository directly (Claude Code, Cursor):
git status
git diff
Read the diff before accepting anything. A tool with file access can change more than you expected, and git diff is the only reliable account of what actually happened.
Commit before letting an agent make a large change, so git restore is a clean undo.
Errors you will hit
| The mistake | Consequence |
|---|---|
| Asking for a whole feature | 400 lines nobody reviews properly |
| Accepting code without reading it | Code you cannot maintain or defend |
| Treating "it compiles" as verification | Six of the eight common failures produce no error |
| Generating tests with the code they test | The tests assert the bug |
| Prompting a fourth time | The task is under-specified — write it yourself |
| Committing without reading the diff | Whatever else the tool changed goes in too |
Compiling is not working. The grading chain with the absent check last compiles perfectly.
Common mistakes
- Asking for a whole feature at once
- Accepting code without reading it
- Treating "it compiles" as verification
- Not checking
SchoolId, soft delete and money types - Generating tests alongside the code they test
- Prompting a fourth time instead of writing it
- Committing without reviewing the diff
- Letting an agent edit files with uncommitted work present
- Accepting extra abstraction you did not ask for
- Blaming the tool for code you committed
Practice
The course exercise is build a feature in verified increments.
- Ask for a complete feature in one prompt. Count the lines and time how long a proper review takes.
- Build the same feature in six steps, verifying each. Compare total time and the number of bugs found.
- Generate a repository method and check it against all eight items in the review table.
- Generate a query and check specifically whether
SchoolIdandIsDeletedare filtered. - Generate grading logic and check the order of the absent check.
- Write your test cases before reading a generated implementation.
- Generate code and its tests in one prompt. Check whether the tests assert the bug.
- Generate the tests separately after reviewing the code. Compare.
- Generate money-handling code and check the type used.
- Prompt three times for something the model keeps getting wrong, then write it yourself and compare the time.
- Ask for a plan for a multi-file change, review the plan, then implement one file at a time.
- Let a repository-aware tool make a change, then read
git diffbefore accepting. Note anything unexpected. - Commit each verified increment separately, then revert one without disturbing the others.
Exercises 1 and 2 together are the argument for this entire article. Do both and compare honestly.
You can now
- Size a change so you can verify it
- Review generated code against the project's rules
- Verify behaviour, not compilation
- Specify test cases yourself, from the requirement
- Know when to stop prompting and write it
Review questions
- Why does a 400-line generated change fail review even when it works?
- Which checks catch bugs that produce no error message?
- Why should tests be written or requested separately from the code?
- When should you stop prompting and write it yourself?