DEV Community

Konstantin Konovalov
Konstantin Konovalov

Posted on

Make it better is the worst thing you can tell an LLM in code review

Vague in, mush out

I asked a model to "make this cleaner." It renamed three variables, rewrote a loop as a comprehension I could no longer read, swapped my error handling for a bare try/except, and reformatted a function I had not mentioned. The bug I actually cared about was still there. I got a diff full of changes I never asked for and none of the one I did.

That is the default behavior, and it is not the model being dumb. When you say "make it better," you have given it no target, so it reaches for the average of everything it has seen. It regresses to the mean. It applies the median opinion about what good code looks like across a million repos, and your specific code, with your specific constraints, gets sanded down into something generic. The stuff you did on purpose looks like a mistake to a model that does not know it was on purpose.

Vagueness is not a small tax here. It is the whole bill. A loose instruction is an invitation to touch everything, and the model accepts.

Build a fence, not a wish

The fix is to stop wishing and start fencing. A good code-review instruction does four things, and skipping any one of them leaks.

Name the exact problem. Not "improve this." Say what is wrong: "this function does two database round trips when one query with a join would do." Now there is a target, and the target is small.

Say what must stay untouched. This is the load-bearing sentence people forget. "Do not change the function signature, the log lines, or the variable names." Without this, the model treats your whole file as fair game. With it, you have drawn a box, and the box holds.

Define good concretely. "Better" means nothing. "Fewer database calls, same public behavior, same return type" means something you can check. Give it a definition it can measure itself against, not a vibe.

Give one example. Show the shape you want, even a tiny before-and-after. One example collapses a thousand possible interpretations into the one you meant. Models are fantastic at pattern-matching an example and terrible at guessing an unstated preference.

Fence it in and the model stops roaming. It fixes the thing, leaves the rest alone, and hands you a diff you can actually read.

Vague versus specific, side by side

Here is the prompt I used to write, the one that produced the mess:

Review this function and make it better.

def get_user_orders(user_id):
    user = db.query("SELECT * FROM users WHERE id = ?", user_id)
    orders = db.query("SELECT * FROM orders WHERE user_id = ?", user_id)
    log.info(f"fetched orders for {user_id}")
    return {"user": user, "orders": orders}
Enter fullscreen mode Exit fullscreen mode

Against that prompt, "better" is a coin flip. The model might rename things, might restructure the return, might decide the log line is noise and delete it. All of that is defensible in the abstract, which is exactly the problem. Nothing tells it where the edge of the request is.

Here is the fenced version:

Problem: this makes two separate database calls for data that could come back in one round trip.
Fix: combine the two queries into a single query using a join.
Keep unchanged: the function name and signature, the log.info line exactly as written, the shape of the returned dict (keys "user" and "orders").
Definition of done: one database call instead of two, identical return structure, behavior unchanged for a user with zero orders.
Example of the join style I want:
SELECT u.*, o.* FROM users u LEFT JOIN orders o ON o.user_id = u.id WHERE u.id = ?

Same model, same code. The second prompt gets you one change: the two queries become one. The log line survives untouched. The return dict keeps its shape. The zero-orders edge case is on the table because you named it. There is nothing to clean up afterward, because nothing extra happened.

The difference is not the model's intelligence. It is how much of the decision you outsourced. "Make it better" outsources the entire spec, so the model fills the vacuum with its average taste. The fenced prompt keeps the spec in your hands and rents the model only the typing.

The rule that scales

This holds well past a single function. In code review, in refactors, in any place you hand work to a model, the amount of unrequested change you get back is directly proportional to how much you left unsaid. Silence is not neutral. Every gap in your instruction is a decision the model will make for you, using preferences that are not yours.

So before you send the next review prompt, read it back and ask one thing: could a competent, slightly overeager junior read this and start rewriting code I never mentioned? If yes, the prompt is too loose. Name the problem. Fence the untouchables. Define done. Show one example. It feels slower for about ten seconds, and then it is faster forever, because you stop reviewing diffs full of changes you have to argue back out.

"Make it better" is a wish. Code review runs on specifications. Give the model a fence and it does the job. Give it a wish and it gives you its average opinion, whether you wanted it or not.

AGINE Academy is an independent product by AGINE AI (not affiliated with Anthropic). We teach building with Claude by doing the work, not watching lectures.

Top comments (0)