Skip to main content
Published / updated

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.

ProblemConsequence
Too large to review properlyYou approve what you have skimmed
Several assumptions embeddedAny of them may be wrong
One bug is hard to locateThe whole change is suspect
Reverting loses everythingIncluding the parts that were correct
You did not design itYou 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

ChangeSizeVerifiable?
One repository method15 linesYes — run it against real data
One endpoint using that method20 linesYes — call it in Postman
One validation rule5 linesYes — send a bad request
One test15 linesYes — it fails, then passes
"The whole fee module"400 linesNo

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:

CheckCommon failure
SchoolId sourceTaken from a method parameter fed by a query string
Soft deleteFilter omitted
Money typedouble or float
Absent handlingCompared before the absent check
SQL parametersString interpolation into the query
asyncMissing await, or .Result
DisposalConnection without using
Exceptionscatch (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:

CaseExpected
Marks 85, passing 35Pass
Marks 20, passing 35Fail
Absent, marks nullAbsent
Marks exactly 35Pass
Passing marks 0Pass

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

SituationDo it yourself
Third attempt still wrongYes — the prompt is not the problem
Business logic you understand betterYes
Security-critical codeYes, or review it as if hostile
A small change you can type fasterYes
Code you would not be able to explainYes
Boilerplate DTOs and mappersNo — 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 mistakeConsequence
Asking for a whole feature400 lines nobody reviews properly
Accepting code without reading itCode you cannot maintain or defend
Treating "it compiles" as verificationSix of the eight common failures produce no error
Generating tests with the code they testThe tests assert the bug
Prompting a fourth timeThe task is under-specified — write it yourself
Committing without reading the diffWhatever 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.

  1. Ask for a complete feature in one prompt. Count the lines and time how long a proper review takes.
  2. Build the same feature in six steps, verifying each. Compare total time and the number of bugs found.
  3. Generate a repository method and check it against all eight items in the review table.
  4. Generate a query and check specifically whether SchoolId and IsDeleted are filtered.
  5. Generate grading logic and check the order of the absent check.
  6. Write your test cases before reading a generated implementation.
  7. Generate code and its tests in one prompt. Check whether the tests assert the bug.
  8. Generate the tests separately after reviewing the code. Compare.
  9. Generate money-handling code and check the type used.
  10. Prompt three times for something the model keeps getting wrong, then write it yourself and compare the time.
  11. Ask for a plan for a multi-file change, review the plan, then implement one file at a time.
  12. Let a repository-aware tool make a change, then read git diff before accepting. Note anything unexpected.
  13. 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

  1. Why does a 400-line generated change fail review even when it works?
  2. Which checks catch bugs that produce no error message?
  3. Why should tests be written or requested separately from the code?
  4. When should you stop prompting and write it yourself?

Next: Debugging and refactoring with AI