In my previous post I argued that the human code review has become the bottleneck in our software delivery process, and that most of what a review used to buy us no longer fits in a diff. But if we stop reading every generated line, when does a human ever look at this code, and what should they be doing when they do?
Somebody still has to understand that code. It was written fast, it passes a demo, and nobody on the team can explain it. Before anyone is willing to put it on production, it needs a review and often by two people. Letting others review code that AI wrote for you is not a good experience, as you may have noticed.
I want to make it a ceremony. Same slot every week, a fixed length, a defined output, like a retrospective. I call it a Grokathon. To grok, from Heinlein’s Stranger in a Strange Land, is to understand something so thoroughly that it becomes part of you. The word is sixty-five years old and predates the chatbot it now shares a name with. Think of it as a hackathon in reverse: nobody adds a feature, and afterwards everybody can explain one.
You understand a codebase by cleaning it up
Ask anyone who has inherited a codebase how they got to know it. Nobody says they read it. They renamed something, moved a file, deleted a function to see what broke, and after a week of that they knew where everything was. You learn a system by getting your hands dirty, not by reading it.
A generated codebase arrives all at once. It runs, the demo looks great, and nobody has ever been through it. Three helpers do the same thing, there is a config option nobody sets, there are defensive branches for conditions that cannot happen, and somewhere in there are the two functions that actually run in production. From the outside it all looks the same.
The ceremony is simple. Everybody takes a piece of it and cleans that piece up by hand. Five rounds, twelve minutes each, and every round is one ordinary refactoring that you already know how to do.
The ceremony
Ninety minutes a week, in the slot you were already spending on reviewing generated pull requests. Two to six people, at least in pairs. The subject is one batch of generated code that has already merged, not a pull request. Everybody clones the same repo, and each pair takes one file or module and works on its own branch.
You always work in pairs. Not for the help, but because your partner acts as a
rubber duck. Renaming a variable quietly is easy. Saying out loud “this one is
called data and it holds the invoices we have not sent yet” is the moment you
find out you were guessing. Hunt and Thomas had programmers explain their code
to a rubber duck for exactly this reason, and a colleague is a duck that
occasionally says “are you sure about that”.
So there is one procedural rule: you may not tidy silently. Say what you are about to do and why, before you do it. Without it you have four people cleaning up code, with it you have a team learning the system. In a pair it mostly takes care of itself.
Two more rules and then the timer. You never take the code you prompted into existence yourself, because you are too kind to your own output and because the point of the session is that this code stops belonging to whoever generated it. And no scoreboard. The only number worth saying out loud is how many things you deleted without the tests noticing. A high number is bad news for the team, and that is why it is worth saying.
| Time | What |
|---|---|
| 0:00 | Divide the work (10 min) |
| 0:10 | Delete (12 min) |
| 0:22 | Rename (12 min) |
| 0:34 | Normalize (12 min) |
| 0:46 | Align (12 min) |
| 0:58 | Test (12 min) |
| 1:10 | Walkthrough (20 min) |
The walkthrough is two minutes per pair: what you deleted, what you renamed, what got past the tests, and what your piece actually does now that you have been through it. Then anything that showed up at more than one desk goes into the rules that produce the code, which is the standards file, the agent skill, the linter config or the prompt. That change is a normal pull request with a normal human review, because a rule that applies to the whole codebase has to be right.
Architectural guidelines should come from here, from people who just spent an hour in the code, rather than from whoever had the strongest opinion in a meeting. A guideline that comes out of a walkthrough can have examples attached, so nobody needs convincing.
It runs fine on a video call and a git remote, which is where most teams already
are. One repo, one branch per pair, no pull requests during the session, and a
dev container or a single docker compose run test so nobody spends the first
round installing a toolchain.
Delete, rename, normalize, align, test
Five rounds, twelve minutes each. You are not trying to understand the code, you are trying to make it neat. The understanding comes as a side effect.
The examples below are not hypothetical. They come from a product we are building right now, where most of the code is generated, the review is ours and the standards live in ADRs. We have run four sessions so far, on Thursday, together, in the slot the code review would otherwise have taken. One caveat so you can judge the rest: the rounds ran looser than the timetable above. More on that below.
Delete. Anything nothing needs: the third helper that does what the first two already did, the option nobody sets, the branch for a condition that cannot happen, the abstraction with one implementation. Remove it, push it, and let CI tell you what you learned. Red means you just found out what it was for, which is worth more than reading it would have been. Green means one of two things: the code was dead and it stays out, or the tests are blind and you just found a hole. Write the hole down, you will need it in an hour.
Then do the same to the comments. Generated code narrates itself, line by line. Delete every comment that says what the code already says, and keep the ones that say why.
The biggest thing we deleted was not code. Building v1 we held every feature up against the question of whether v1 needed it, and a cleanup sweeper for stale data lost that argument and moved to v2. A diff review never asks whether a feature should exist, because by the time somebody is reading the diff that question is long settled. This round asks it about everything you touch.
Rename. Name things after what they hold. Generated code is full of data,
result, processedItems and handleData, names that describe the
conversation the model was having rather than the value in the box. You cannot
rename something correctly without knowing what it does, which makes this the
cheapest way to find out that you do not.
Two of ours: a variable called calculated became calculatedScores, and a
function called ensure became readOrStore. Both moves go against the advice
you usually get. The first is redundant, because scores were the only thing
around there that could be calculated. The second is longer and duller, and it
trades a nice metaphor for a flat description of the two things the function
does. Redundant and dull is where this round kept ending up, because the next
reader has to work out what the thing is without asking anybody, and the next
reader is often a machine.
Normalize. Normalize symmetries, in Kent Beck’s phrase: make things that do the same job look the same, and things that do different jobs look different. Three functions handle a missing record in three shapes, one returning null, one throwing, one handing back a tuple. Generated code has almost no symmetry, because every piece was written in a separate conversation without seeing the others. Doing this straight after the renaming helps, because consistent names are what make the inconsistent shapes visible.
In practice our asymmetries were less obvious than three return shapes. They were responsibilities: the same job sitting in a method of its own in one place and inlined in another, and an architectural pattern that the surrounding code follows and this one piece quietly skips.
Align. Every team has a shape it wants its code to have: which layer talks to the database, where validation happens, how errors travel, what a module looks like from the outside. Your piece does not have that shape, because the generator never read that document, or read it once and drifted. Move it back. This is also the round that tells you whether your guidelines are real, because if you cannot say what the rule is for the code in front of you, that is not a failure of yours, it is the finding, and it goes to the walkthrough.
Held against real code, a fair number of clauses in our ADRs turned out to be impractical, and we rewrote them. That felt wrong at first, but if you cannot follow a rule in the code as it exists, the rule is the problem. Rewriting the clause is what makes the ADR usable again, and the ADR is what the next agent reads.
Test. Every hole the deleting exposed gets a test. Write the one that fails when you take that code out again. Not coverage, not a green wall, just the test that would have caught the thing that got past the suite an hour ago. Then take it out one more time and watch it go red, because you should see a test fail at least once before you trust it.
We ended up writing our doubts down in TODOs as we read, and this round worked from that list rather than from coverage. If anybody was unsure whether a behaviour was right, that was the test to write. Doubt is easy to put into words while you are looking at the code and gone by the next day, so it has to be written down on the spot.
Yes, the tests come last, and normally I would argue the opposite. Here the missing tests are what we are trying to discover, so the first four rounds use the suite you have as an oracle and the last one repays what it could not tell you.
The rounds do not run in order
That is the part I had wrong. On paper the five rounds are a sequence and the timetable above still says so, because ninety minutes needs a structure and the order is a good default: renaming makes asymmetry visible, aligning tells you what is worth testing. In the code it does not behave. You are renaming something and you spot the branch that cannot fire. You are deleting a comment and you find the clause in the ADR that never made sense. The finding arrives when it arrives, and it is almost never for the round you are in.
What worked for us is a TODO in the code at that exact spot, one line, with
the applicable round and what you doubted or still (may) want to fix. Then you
go back to the round you are in. The next round picks the TODOs up, and whatever
nobody picks up goes to the walkthrough, because a doubt that survived five
passes is worth the whole table’s attention. It also handles the good idea you
get two minutes into a twelve minute round, which otherwise either derails the
round or is gone by the end of it.
So treat the rounds as five passes over the same code with a different question each time, rather than five jobs to finish in turn. The timer exists to make you move on before you disappear into one file.
Why this can replace the code review
Sam Newman lists four goals of a code review, and this session serves all four better than reading a diff does. Correctness is checked by the deleting and the testing, which put the test suite itself to the test, and that suite will still be checking correctness next month. Shared learning is the entire session, out loud, in both directions, where a pull request comment teaches one author. Alignment comes from the guidelines the walkthrough produces, and those scale because they change the generator instead of the output. And awareness follows from everyone having their hands in code they did not write.
The merge gate does not change. Tests green plus policy satisfied, decided by something deterministic, exactly as before. All that changes is what the people do.
That is the theory. In practice, after four sessions, I cannot tell you they were faster, more thorough or better than the reviews they replaced. I did not measure any of that, and I do not believe the faster part anyway, because ninety minutes a week is ninety minutes a week.
What I can tell you is that they were more fun, and I no longer see that as a side benefit, because it comes from ownership. A code review asks somebody to approve, which means putting your name under code you did not write. Defending a diff that a machine wrote is not fun. This session asks nobody to claim anything up front. You take a piece, you clean it up, you say out loud what it does, and by the end it belongs to the team instead of to whoever prompted it. Fiddling with the code together to make it nicer and understand it better is why most of us became developers.
Tidy First? assumes a human author
None of these moves are mine. Kent Beck names them in “Tidy First?”. Delete redundant comments and normalize symmetries are in his list almost word for word, and explaining variables is the renaming round.
But the title of that book is a question. Beck does not tell you to tidy, he tells you when it pays. His case rests on two things. It is social: a person wrote this code and a person will read it next, so structure is a courtesy between colleagues. And it is economic: tidying costs your hours now and pays them back on the next change, so you tidy first when that change gets cheaper by more than the tidying costs. That is why the book also has chapters called Tidy Later and Tidy Never.
Neither is true anymore. Nobody wrote this code, so there is no author to be courteous to and no colleague who was going to read it. And nobody paid for it, so nobody will pay for the mechanical rewrite either, because that is precisely the work the machine now does for nothing.
His answer was “it depends”, and what it depended on has changed. Tidying still matters, but for different reasons.
Tidying is now the only way to understand the code. There is no author to ask, no commit message that explains a decision and no colleague who remembers the trade-off. Reading does not work at this volume, and asking the model gives you a plausible answer rather than a true one. Renaming a variable correctly is a test of understanding you cannot pass by accident.
The structure of the code also matters in a new way. It used to be for the humans who would read the code later. Today it is the context the next agent reads before it writes, so tidy code leads to better generated code and untidy code to worse. Beck’s economics had no path from the structure back into the thing that produces it. Now there is one.
Asymmetry means something different too. Beck normalized symmetries because symmetric code is easier to read. In generated code, two things that do the same job in two different shapes were written in two different conversations, neither of which saw the other. That is where the misunderstandings are, so normalizing symmetries is also a way to find them.
Nothing slows coupling down anymore either. Constantine’s equivalence still holds, and Beck could take for granted that coupling accumulates slowly because writing code is slow. A thousand coupled lines is a minute of work now, and removing them costs exactly what it always did.
Finally, tidying is how you take ownership of code you did not write. You cannot be responsible for something you have never touched, and somebody has to be. That is not an economic argument at all.
What still holds is the discipline. Structure and behavior never go in the same commit, which is why a Grokathon branch is structural from start to finish and safe to merge the same day. Batches stay small. And Tidy Never is still a real answer, which is why you do not run this on a prototype you are about to throw away.
What to leave to the agent
If applying a tidying is free now, the obvious move is to hand all five rounds to the agent and have a clean module in ninety seconds. It would do a decent job, but you would have paid for the session and received nothing, because the output of a Grokathon was never the diff.
So what is tidying, actually?
Every piece of work produces two things: the artifact, and the change in the person who made it. We only ever pay for the first. The second is a side effect nobody measures, and that was fine, because for most work it is a bonus. Automation is the trade where you keep the artifact and give up the side effect, and for most work that trade is obviously worth taking.
Tidying is the exception, because for tidying the side effect is the product. Which gives you a test you can apply to any task, not just this one. Throw away the diff and see what is left. If nothing is left, delegate it. If what is left is a person who knows something they did not know this morning, that is tidying, and handing it over buys you nothing at all.
Put differently, it is you who needs the tidying, not the code. Sending the agent to tidy for you is sending somebody else to the gym and asking them how it went.
You can still use the agent during the session. Each of the five rounds consists of a decision and an edit, and you only give it the edit. It can rename every occurrence in the repository, but it cannot choose the name, and choosing the name is the entire exercise. It can apply your architectural guidelines, but it cannot tell you that a guideline is wrong, which is what the align round is for.
So agents are allowed during the ninety minutes, as long as you let them type
and not decide. “Rename data to pendingInvoices everywhere in this file” is
fine, and a better use of everyone’s time than doing it by hand. “Clean up this
file”, “suggest better names” and “make this consistent” are not fine, because
that is the session itself.
Understanding cannot be scheduled, but deleting, renaming and testing can, and they reliably produce it. You are still responsible for what you ship, and this gives you ninety minutes a week to make that true.