Code Review Without Making Enemies: 5 Rules I Learned

I once gave 10 critical comments without solutions, junior burned out for 3 days. Proper code review makes both code and author grow, not make the dev feel worse. 5 rules I learned.

· · 8 min read

I remember my first code review of a junior. There was a 200-line function handling auth. I wrote in the comment: "This function is too long, refactor into smaller pieces. Use early returns, don't nest ifs. Move logic to a service. And the naming is not descriptive enough."

10 comments. All critical. The junior read them, went silent for 3 days, then did a half-hearted refactor. Result: the function was split into 4 parts but still messy, because he refactored without understanding why. I was frustrated. He burned out.

That's when I realized: my code review wasn't a review. It was critique without solutions. I gave 10 problems with no context, no examples, no priorities. The junior was overwhelmed, I was disappointed. A proper code review should make the code better, not make the dev feel worse.

These are 5 code review rules I learned the hard way - from making enemies to growing the team.

---

Rule 1: Comment on the Problem, Not the Person

Wrong:

"Why did you write it like this, didn't you read the docs?"

Right:

"This approach has a potential issue if userId is null. Maybe add a null check before findUser?"

Difference: the first attacks the person ("you"). The second attacks the problem ("this approach"). A junior reading the second knows what to do. A junior reading the first just feels attacked.

Practical rule: replace "you" with "this code/this function/this approach." If you really must be personal, use "we" - "we could try an alternative approach." Collective, not accusatory.

Code review is about the code, not the coder. You review the work output, not the person's identity.

---

Rule 2: Not Just "What's Wrong," But "Why" and "How to Fix"

Useless code review:

"The data naming isn't descriptive."

Proper code review:

"The data variable could be more descriptive. Since it contains userTransactionHistory, maybe rename it to userTransactions for clarity. What's it used for? If it's for the dashboard display, maybe recentTransactions is better."

Difference: the first just states the problem. The second states the problem + why + solution + a context question. Reviewer and author become a discussion, not judge and defendant.

Format I use:

[Issue] The data variable isn't descriptive
[Why] The reader doesn't know what's in it without reading further
[Suggestion] Rename to userTransactions or recentTransactions
[Question] Where is this variable used besides this function?

4 lines. Longer than just "rename data." But the author gets context, solution, and a chance to discuss. Code review becomes a dialogue, not a monologue.

---

Rule 3: Prioritize Problems - Not All Are Equal

I used to give 20 comments without distinguishing critical from nitpick. The author read all of them, had to prioritize themselves. Result: they fixed the easy stuff first (whitespace, naming), then the critical stuff (logic bugs). Wrong order.

Now I use labels:

• [Blocker] - must fix before merge. Logic bugs, security issues, data loss.
• [Important] - should fix, but can merge first + follow-up issue. Performance, unhandled edge cases.
• [Nitpick] - optional. Style, naming preferences, comment clarifications. Can skip.

Example:

[Blocker] SQL injection on line 45 - query is string-concatenated, must be parameterized
[Important] This function has N+1 queries, can be optimized with JOIN
[Nitpick] Variable i could be index for clarity

The author knows: fix Blockers first, Important anytime, Nitpick if you have time. You're not forcing 20 fixes at once. You're giving a roadmap.

Don't overuse Blocker. If you label everything as Blocker, the author panics. If you label 3 out of 20 as Blocker, the author knows: these 3 are critical, the rest is negotiable. Restraint is a skill.

---

Rule 4: Don't Write Replacement Code - Discuss First

I used to often write solution code directly in comments:

• const data = await fetchUser(id)
• const user = await fetchUser(id)
• if (!user) throw new NotFoundError()

Looks helpful. But the problem: the author doesn't understand why. They copy-paste, merge, done. Next time, they make the same mistake because they didn't learn.

Now I discuss first:

"Line 12: fetchUser can return null if the user doesn't exist. How do you want to handle it? Throw an error? Return 404? Null object pattern? I'd suggest throwing NotFoundError to be consistent with findOrder in file X, but you know the context better."

The author has to think. You give options + suggest + reasoning, but the author decides. If they come back and ask "why throw, not return null?", you can explain the trade-off.

Except in emergencies. Production is down, quick fix, I write the solution code directly. But I comment: "This is an emergency fix, we'll discuss the best approach later." Not "this is the right way, your way was wrong."

---

Rule 5: Praise Is Important Too - Code Review Isn't Just Finding Faults

A code review that only critiques makes the author feel like their work is never right. But maybe 90% of the code is good, only 10% needs fixing. If you only comment on the 10%, the author doesn't know the 90% was good.

Now I always give specific praise:

[Good] Error handling on line 23 is clean, uses custom error class, informative logs
[Good] Test coverage is thorough, the null edge case is covered
[Good] The calculateRefund function is small and focused, nice

Specific praise is different from generic praise. "Nice code!" is useless. "Error handling is clean, uses custom error class" tells them what's good, so the author knows to keep up that pattern.

Philosophy: code review aims to raise code quality, not lower author morale. If the author feels their work is appreciated, they're more open to critique. If the author feels attacked, they become defensive. Defensive author = doesn't accept feedback = code doesn't improve.

---

Quick Comparison: Toxic vs Healthy Code Review

| Aspect | Toxic Review | Healthy Review |
|---|---|---|
| Tone | "Why did you..." (personal) | "This code..." (impersonal) |
| Content | Problem only | Problem + why + solution + discussion |
| Priorities | All equal (author confused) | Blocker/Important/Nitpick labels |
| Solution | "Here's the correct code" (pushed to author) | Discuss first, author decides |
| Praise | None | Specific to good patterns |
| Result | Author defensive, code doesn't improve | Author learns, code gets better |
| Team | Burnout, turnover | Psychological safety, growth |

---

Common Misconceptions

"Code review = finding all the bugs." - Wrong. Code review = making sure code meets requirements + identifying risks + sharing knowledge. If you're just finding bugs, you're QA. A reviewer is different from QA.

"The reviewer must find at least 5 comments." - Toxic quota. If the code is already good, say so. Don't nitpick just for a quota. Unimportant nitpicks create noise, hiding the important comments.

"The author must accept all feedback." - Wrong. The author can reject with reasoning. Code review is a discussion, not a decree. If the reviewer comments "rename data to userData" but the author prefers userTransactions, discuss. Don't say "I'm the reviewer, you're the author, you obey."

"Senior devs don't need to be reviewed." - Fatal. Senior devs make mistakes too. Code review isn't about junior vs senior. It's about a second pair of eyes. Linus Torvalds still gets reviewed on the Linux kernel. Ego = the enemy of code quality.

---

An Honest Closing

What I've learned about code review over the last 5 years: it's not about judging someone else's code, but about making both the code and the author grow. You give actionable feedback, the author gets insight, the code gets better, the author gets better. Win-win.

The philosophy I learned: code review is a dialogue, not a verdict. The reviewer isn't a judge, the author isn't a defendant. Both are contributors who want the code to be better.

5 rules I use now:
1. Comment on the problem, not the person (replace "you" with "this code")
2. Problem + why + solution + discussion (not just the problem)
3. Priority labels (Blocker/Important/Nitpick)
4. Discuss first, don't push solution code directly
5. Praise specifically for good patterns

If you're a reviewer who likes to judge, try to remember: the first time you learned to write code, you also wrote 200-line functions like that junior. You weren't instantly good. A senior dev isn't someone who never makes mistakes, but someone who has made mistakes and learned. Give juniors the same opportunity you got.

If you're an author who often gets harsh reviews, remember: the feedback is about the code, not about you. Take what's useful, skip what's toxic. But if the reviewer is consistently toxic, maybe the team needs to be reviewed.

---

Sources

• Google Engineering Practices: Code Review Standard
• Google Engineering Practices: What to Look For