跪拜 Guibai
← Back to the summary

AI Writes Code So Fast, Reviewers Spend Their Time Tearing It Down

What I Fear Most Now Isn't That AI Can't Write Code, It's That It Writes Too Fast — Too Fast for Me to Say "Wait a second"

Last Wednesday afternoon, I reviewed a PR.

More than 300 lines, a React component plus a bunch of utility functions, from a new teammate. The code was written by Cursor, he changed a few spots, tests passed, UI ran fine.

I looked at it for 20 minutes and merged it.

On my way home that night, it hit me: out of those 300 lines, I didn't fully understand what about sixty or seventy of them were doing. Not because the code was bad — variable names followed conventions, types were complete, comments were there. But there were a few bits of logic where I knew it "worked," but I couldn't explain why it was written that way.

That feeling is uncomfortable. Before, when reviewing someone else's code, if I didn't understand it, I'd ask them to walk me through it, or get a glass of water and read again. But this time was different — the code was correct, tests passed. The thing I was stuck on: I couldn't find a concrete reason to ask him to change it.

1. In the past we said "the code is slow," now we say "would you even look at it?"

When I first entered the industry, the biggest fear in a team was people who wrote code fast but never wrote tests. They'd submit piles of code, run into issues every few days, and during review you could pull out a handful of "missing null check here" and "this edge case isn't handled."

Back then, review had a clear target. Logic bugs, messy naming, missing error handling — you'd see it and say "this doesn't work, go fix it."

Now the target has gotten blurry.

AI-generated code has a very high floor. Null checks are there, edge cases are there, error handling is there, naming is pretty standard. You open the diff and your first impression is "it's neater than what I write."

But you still feel something's off. Then you scroll down and find a piece of logic that uses three nested useEffects to compute something a useMemo would have handled. Or a component that crams in three different data-fetching approaches — one fetch, one useSWR, and one passed down from props.

It's not wrong, it's "unnecessary."

And that "unnecessary" is the hardest thing to review. You can't point at code that runs, passes tests, and has no security problem and say "dude, this abstraction is a bit redundant." Because the other person will likely respond, "then how would you write it?"

For a moment, you actually can't answer that.

2. Lately, the thing I do most is "tear it back down"

Last month we were building an internal data dashboard. I started with Claude Code myself, told it "help me build a table page with filtering and pagination."

Ten minutes later, the page was there. Filter, table, pagination, loading state, empty state — all there. I ran it, it was fine. Then I asked it to add an "Export to Excel" button.

Two more minutes, done.

Then I asked it to "sync the filter conditions to the URL so refreshing the page restores them."

It did it. Using useSearchParams plus a useEffect listener, the logic was fairly complete.

But when I opened the file, it was already over 400 lines. One table page, over 400 lines. Inside there were three useEffects, two useStates managing two copies of the same data, a useRef holding "the previous filter value," and a useCallback wrapping a function that was actually called only once.

I sat there for five minutes, then started tearing it back down.

Merged the two useStates into one, removed the useRef, dropped the useCallback, moved the logic from one of the useEffects into an event handler.

After tearing down, it was just over 200 lines.

Not a single feature lost, tests still passed, but it took me forty minutes.

Those forty minutes weren't because AI wrote bad code. It was because when AI writes, nobody stops it. Every time you say "add another feature," it piles another layer onto the file. It doesn't know where "enough" is. It only knows "I gave you everything you asked for."

3. Now, when I review, I ask a stupid question

"This code — if tomorrow product says the requirements change, which line do you start touching?"

That question is crude and not rigorous. But I've found it works extremely well.

There's a subtle problem in AI-written code: it often puts the "easiest place to modify" in the "hardest place to read."

For example, it splits a state calculation's logic into three different useEffects, each handling a small piece. It runs correctly, because React executes them in order. But tomorrow when you want to change the calculation rule, you have to open all three places at once, and simulate their execution order in your head.

Ask AI "why split into three," it gives a plausible explanation: "separation of concerns."

But you know the real concern is only one: will this logic be touched by someone tomorrow?

My current approach: any place where I find "three or more useEffects working together" in review, I ask to merge them. Not because it's wrong, but because I can't afford the time to change it next time.

4. One thing I still haven't figured out

There's a teammate who uses AI more aggressively than I do. He basically doesn't hand-write components anymore, leaves it all to agents. But his code reviews are particularly comfortable.

I watched him for a while and noticed a habit: after every AI generation, he rewrites it himself.

Not fully — he breaks apart the structure AI wrote and reorganizes it according to his own habits. Variable names become the abbreviations he likes, functions get split into his preferred granularity, comments are cut in half.

I asked him: "doesn't that mean you basically had AI write it for nothing?"

He said something I've remembered ever since:

"AI writes the parts I don't know how to write. The parts I do know how to write, I don't let it touch."

That sounds like chicken soup. But later I tried it, and it really was different.

Before, I'd "let AI write the whole component, then I'd edit." Now I "first write the skeleton myself, define the data flow and state boundaries, then let AI fill in the details."

The latter is slower, but review goes much faster. Because the skeleton is mine; I can read it.

As for why the former makes me uncomfortable — I still haven't fully figured it out. Maybe it's the idea of "not understanding your own code" that's unsettling, even if the code is correct.

5. Let's talk about something practical

If you're also leading people, or reviewing code AI was involved in, I have three immature pieces of experience:

First, don't review directly in the diff. Download the file, open it locally, and look at the whole file. AI-written code looks neat in a diff, but reading the whole file often feels like "every section is fine, together it's weird."

Second, when you see "three or more useEffects depending on each other," ask to merge them. This isn't a code-style issue; it's a maintenance cost issue. AI likes to use multiple useEffects to "process step by step," but it doesn't bear the cost of having to change three places next time.

Third, ask that stupid question: "if requirements change tomorrow, which line do you touch?" If the other person (or yourself) can't answer, the structure is problematic, no matter how correctly it runs.

Finally, a sentence that might not be pleasant.

AI has made the cost of "writing code that runs" very low. So low that sometimes I feel reviewing is more tiring than writing.

But tiring as it is, you have to look. Because when a bug hits production, users don't blame Cursor. They blame you.

Comments

Top 1 of 3 from juejin.cn, machine-translated. The original thread is authoritative.

歘chua

Bigshot, give some guidance, what about refactoring? Recently upgraded an internal npm package, too many methods and contents to replace. When the previously agreed logic was being migrated gradually, it stopped being written according to the rules. If I implement according to your method, it feels like too much content, and I can't keep up with time and speed.

涛涛ing

First add a layer of adapter/wrapper functions, make the old logic compatible first, replace in batches by call sites; during the migration period, only seek consistency not elegance, set a deadline, after migration do unified refactoring, tests as a safety net, don't decorate while moving.

歘chua  → 涛涛ing

Thanks for the guidance, bigshot.