✍️ 필사 모드: Authoring Reviewable Pull Requests: Small PRs, Good Descriptions, and Stacked Diffs
English"A good PR is a gift to the reviewer. A bad PR is homework dumped on them."
Prologue — The author controls review latency
"Why does my PR always take so long to merge?" Many engineers look for the answer in the reviewer: they're busy, it's low priority, the team is slow. Some of that is true. But the biggest variable lives somewhere else. A PR's review latency is mostly decided by its author.
When a reviewer opens a PR and finds an 800-line diff with a one-line description ("add feature"), that PR gets pushed to "later." Reviewers are people too, and work that demands a heavy cognitive load naturally slides to the back of the queue. Conversely, a 120-line diff that clearly states "what, why, and how to verify" gets finished in the five minutes a reviewer waits for coffee.
This is not a post about how to do code review. That's the reviewer's craft — review etiquette, merge queues, review prioritization belong to the receiving side. This is a post about how to author a PR: everything the author can do to make the reviewer's job easy and fast.
A good PR is consideration, not negotiation
People who see a PR as "the gate my code has to pass through" and people who see it as "a place to verify code together with a reviewer" produce completely different PRs. The former makes the diff big, writes a short description, and answers questions defensively. The latter cuts the diff small, supplies enough context, and guides the reviewer on where to start.
Consideration is not an abstract virtue. It is a set of measurable behaviors — cutting the PR small, writing the verification steps in the description, separating formatting changes, doing a self-review first. This post is a list of those behaviors.
What this post covers:
| Chapter | Topic |
|---|---|
| 1 | Why small PRs win — the cognitive cliff |
| 2 | One PR = one idea |
| 3 | The PR description — your most important artifact |
| 4 | Making the diff readable — commit hygiene |
| 5 | Stacked PRs / stacked diffs |
| 6 | Self-review before requesting review |
| 7 | Responding well to review feedback |
| 8 | Draft PRs and signaling readiness |
| 9 | Authoring PRs in the AI era |
| Epilogue | Checklist + anti-patterns + next post teaser |
Chapter 1 · Why small PRs win
If you had to pick the single most effective piece of PR-authoring advice, it would be: make it small. Every other technique stacks on top of this one.
1.1 Review quality is inversely proportional to PR size
A reviewer's focus is not infinite. As the diff grows, the attention spent per line shrinks. So big PRs produce a paradox — the more you change, the less each change is examined.
| PR size | What the reviewer actually does | Result |
|---|---|---|
| ~50 lines | Reads and thinks about every line | Catches bugs and design issues |
| ~200 lines | Reads the core, skims the rest | Catches only surface-level issues |
| ~500 lines | "Looks fine, no big problems" → approve | Effectively unreviewed |
| 1000+ lines | Approves with guilt, or defers indefinitely | Review becomes a formality |
An "LGTM" on a 500-line PR is not a review — it's a surrender. If what the author truly wants is scrutiny, they have to hand over a scrutinizable size.
1.2 The cognitive cliff
The relationship between PR size and review quality is not linear. Past a certain point there is a cliff where it drops off sharply. The exact number varies by team and codebase, but empirically that cliff sits somewhere around 200 to 400 lines.
Below the cliff, the reviewer can hold the whole PR in their head at once. Above it, that becomes impossible, and the reviewer starts reading the PR "in pieces." Reading in pieces means missing the interactions between the pieces — which is exactly where bugs hide most.
The goal is simple: keep every PR below the cliff. If the work is large, you cut the work — you do not grow the PR.
1.3 The compounding effect of small PRs
Small PRs don't just raise review quality.
- Fast feedback — Small PRs get reviewed fast, merged fast, deployed fast. If the direction is wrong, you find out early.
- Easy rollback — When something breaks, the unit you revert is small. Instead of hunting through 800 lines for the culprit, you revert one 80-line PR.
- Fewer conflicts — Small PRs don't live long, so they have no time to drift from
main. Rebase hell shrinks. - Clear history —
git logbecomes a record of meaningful units of change, not a giant "add feature" commit.
Going small is not a cost; it's an investment. The ten minutes spent cutting prevents the multi-day delay a big PR creates.
Chapter 2 · One PR = one idea
Just as important as going small is carrying only one thing. Even a small PR becomes hard to review when two unrelated changes are mixed into it.
2.1 The "no mixed PRs" rule
The most common and most harmful anti-pattern is mixing a refactor with a behavior change in one PR.
The reviewer looks at a changed function. The name changed, the location moved, the indentation is different, and somewhere inside it one if condition changed. The reviewer now can't ask the question — "Is this behavior change intentional, or a mistake made while refactoring?" The noise has buried the signal.
The rule is simple.
One PR carries one idea. Never put a refactor and a behavior change in the same PR.
The order is fixed too: refactor first, behavior change second. A refactor PR can declare "behavior identical, structure only changed," and the reviewer can skim it fast. Then the behavior-change PR arrives as a small diff on a clean foundation.
2.2 What counts as "one idea"
| Wrong bundle | Correctly split PRs |
|---|---|
| Feature A + a refactor for Feature A + an unrelated typo fix | PR 1: refactor / PR 2: Feature A / PR 3: typo |
| Bug fix + a dependency upgrade done "while we're at it" | PR 1: dependency upgrade / PR 2: bug fix |
| New API endpoint + whole-file reformatting | PR 1: formatting / PR 2: endpoint |
| Feature + the tests for that feature | Both in one PR — the tests are part of the feature |
The last row is the key. "One idea" is not "one file" or "one commit." A feature and its tests are the same idea, so they go together. An unrelated typo, however small, is a different idea, so it gets separated.
2.3 "While I'm at it" is a warning siren
While building a PR, temptation arrives — "while I'm in this file, let me delete that dead code," "while I'm here, let me rename that variable." That "while I'm at it" is the exact moment a mixed PR is born.
The principle: when "while I'm at it" pops up, stop, and turn it into a separate PR or ticket. This is not about throwing away a good discovery. It's about moving that discovery into its own reviewable unit.
Chapter 3 · The PR description — your most important artifact
The most-used and least-cared-for part of a PR is the description. Many authors spend two hours on the diff and twenty seconds on the description. That's backwards.
3.1 The description answers what the diff cannot
The diff shows what changed. What the diff can never show is why. And the central question of review is almost always "why" — "why this way," "why not another way," "why now."
Without a description the reviewer has to guess the "why," the guess is wrong, and review comments built on a wrong guess burn the time of both author and reviewer.
3.2 The six elements of a good PR description
| Element | Question it answers | What happens without it |
|---|---|---|
| What | What does this PR change? | The reviewer has to read the whole diff and summarize it |
| Why | Why is this change needed? | The reviewer guesses the intent |
| How | What approach did you take, and why that one? | The design intent doesn't get communicated |
| Verification | How did you confirm it? | A "did you test it?" round trip happens |
| Out of scope | What is not being done this time? | "Shouldn't this be fixed too?" comments pile up |
| Screenshots | How does the UI change look? | The reviewer pulls the branch and runs it themselves |
Of these six, "out of scope" is the most often missing and the most effective. A single line — "this PR does not do X — that's a follow-up PR" — preempts five comments the reviewer was about to write.
3.3 A reading guide for the reviewer
When a large change is unavoidable (for example, when generated files are included), write the reading order in the description.
## Review guide
- The core logic is in `src/auth/session.ts`. Start here.
- `src/generated/*` is codegen output. Skim and move on.
- The `package-lock.json` change is a side effect of the dependency addition.
This one block changes the felt weight of "the diff is 600 lines" into "you actually need to look at 80 lines."
3.4 A copyable PR description template
Below is a template you can copy directly into .github/pull_request_template.md or pin in your team wiki. The outer fence uses four backticks because it contains code blocks and ## headers inside.
## What
{What this PR changes, in one or two sentences}
## Why
{Why this change is needed. Linked issue: #123}
## How
{The approach taken and why. One line on any alternative considered and dropped}
## How to verify
- [ ] {A check step the reviewer can follow}
- [ ] {Tests added / modified}
- [ ] {Manual verification steps — if any}
```bash
# Reproduction / verification command — if any
npm test -- src/auth
```
## Out of scope
- {What is not being done this time — and where it will be handled}
## Screenshots (for UI changes)
| Before | After |
| --- | --- |
| {img} | {img} |
## Review guide (when the diff is large)
- {Where to start}
- {Files you can skim and move past}
You don't have to fill every field. If there's no UI change, delete the screenshots field. But keep the three fields — What, Why, Verification — in every PR. Those three cut 90% of review latency.
3.5 The title is a description too
The PR title lives in git log forever. Titles like "fix," "update," "wip" give zero information six months later.
- Bad:
update - Bad:
bug fix - Good:
auth: add automatic token refresh on session expiry - Good:
fix: disable the checkout button on an empty cart
The rule: the title states, in one line, what changes once this PR is merged. Signaling the area and the kind with a prefix (auth:, fix:) is even better.
Chapter 4 · Making the diff readable — commit hygiene
Even if you made the PR small and single-idea, review is still hard if the commits inside it are a mess. The diff has to be structured to be read.
4.1 Logical commits
A reviewer reads a PR in two ways — the whole diff at once, or commit by commit. For the latter to be possible, the commits have to be logical units.
| Bad commit history | Good commit history |
|---|---|
wip | auth: add the SessionToken type |
fix | auth: implement the token expiry check |
fix again | auth: wire up auto-refresh near expiry |
oops | test: add tests for session refresh scenarios |
address review | (review responses: see 4.3) |
In a good history the reviewer can walk commit by commit and understand "why this change happened in this order." Ideally each commit, on its own, passes the build and tests.
4.2 Formatting changes go separate
Running an auto-formatter (Prettier, gofmt, black, etc.) fills the diff with meaningless lines. Those lines bury the real change.
The principle: mechanical changes like formatting and renaming go in a separate commit, and ideally a separate PR. A single "whole-file formatting" commit lets the reviewer skim it in one second with git diff -w (ignore whitespace). But mix it into the same commit as a behavior change, and the reviewer has to inspect line by line.
4.3 Cleaning up history before review
Before opening the PR, clean up the wip / fix / oops commits that piled up during work.
# On the work branch: restructure the last 5 commits into logical units
git rebase -i HEAD~5
# Or squash everything and re-split it logically
git reset --soft main
git add -p # pick changes into meaningful units
Caution: do not casually rewrite history on a PR where review has already started (see the rest of 4.3 and Chapter 7). As a rule, finish the cleanup before the first review request.
4.4 Don't mix in one-line unrelated changes
When the diff contains a line where only import order changed, or a line where unused whitespace was removed, the reviewer has to stop every time and judge "is this intentional?" Filter out editor-generated changes with git add -p before committing.
Chapter 5 · Stacked PRs / stacked diffs
Sometimes you want to cut work small but the changes depend on each other — PR 2 only works on top of PR 1's code. This is where stacked PRs are the answer.
5.1 What stacked PRs are
Stacked PRs are PRs stacked in a line. Each PR is based not on main but on the branch of the PR directly below it.
main
└─ PR 1: add auth types (base: main)
└─ PR 2: token validation (base: PR 1)
└─ PR 3: wire auto-refresh UI (base: PR 2)
The reviewer goes through PR 1 onward in order. Each PR is small, carries one idea, and the diff shows only the difference from the PR directly below. A single 800-line PR becomes four 200-line PRs.
5.2 When to use a stack
| Situation | Stack? |
|---|---|
| A big feature can be split into dependent steps | Yes — the classic use of a stack |
| Refactor → behavior change on top of it | Yes — PR 1 refactor, PR 2 behavior |
| The changes are independent of each other | No — just open separate PRs in parallel |
| The steps go beyond five | Caution — a too-deep stack has high management cost |
Use a stack only when there's a dependency. If you stack independent changes for no reason, then when a lower PR is blocked, the upper PR is blocked with it.
5.3 The reality of operating a stack
The biggest cost of a stack is that when a base PR changes, you have to rebase every PR above it.
# PR 1 changed from review feedback → rebase PR 2 back onto PR 1
git checkout pr-2-branch
git rebase pr-1-branch
# Updating the whole stack is better done with tool help
# (Graphite, Sapling, spr, and other stack-specific tools)
Manage a stack by hand and the rebases quickly become hell. For teams that use stacks often, adopting a stack-specific tool (Graphite, Sapling, spr, ghstack, etc.) is nearly essential. The tool handles rebasing and re-submitting the whole stack in one shot.
5.4 The fallback when you can't use a stack
If you have no stack tooling or the team isn't comfortable with stacks, the fallback is splitting the steps into logical commits within a single PR (see 4.1). Tell the reviewer in the description, "please review commit by commit." It isn't as clean as a stack, but it's far better than one giant monolithic diff.
Chapter 6 · Self-review before requesting review
There is one thing to do before pressing the request-review button: look at your own PR through the reviewer's eyes.
6.1 Why self-review works
While working, you're trapped in the viewpoint of the person "writing" the code. Push the PR and look again on the diff screen, and the viewpoint shifts. Things invisible in the editor become visible in the diff view — a left-behind console.log, commented-out code, a debug hardcode, an undeleted TODO, a missing error handler.
You should not expect the reviewer to catch these. That makes the reviewer spend their attention on trivia and strips away the bandwidth to look at the design.
6.2 Self-review checklist
Before requesting review, open the "Files changed" tab on GitHub/GitLab and look line by line.
- No debug traces (
console.log,print, commented-out code, hardcoded values) - No unrelated changes mixed in (formatting, import order, accidental whitespace)
- Every new branch has a test
- Errors and edge cases are handled (empty values, null, failure paths)
- The "Verification" field in the PR description is something I can actually follow
- The diff is below the cliff — if not, can I cut it?
- The commit history is logical
- The first line the reviewer hits is the most important line
The last item is subtle. You can adjust file order or commit order so that the most important change is the most visible within the diff.
6.3 Self-review saves time
Spend five minutes on self-review and you cut one review round trip. One review round trip is usually half a day to a full day (including the wait until the reviewer frees up again). Five minutes versus half a day. There are few investments with a higher return.
Chapter 7 · Responding well to review feedback
Once you open a PR, comments arrive. Here the author's attitude decides even the review speed of their next PR.
7.1 Respond to every comment
A review comment should end in one of two ways — you applied it, or you explained why you didn't. A comment left with no response signals to the reviewer that "my opinion was ignored."
| Comment type | A good response |
|---|---|
| A clear bug pointed out | Fix it and say "fixed (commit abc123)" |
| A better approach suggested | Apply it if you agree; if not, "I chose this way because X. What do you think?" |
| A matter of taste | If trivial, just apply it (cost of arguing > cost of applying), or cite the team convention |
| A question | Answer with a comment, not just code, and add a code comment if needed |
| An out-of-scope suggestion | "Good catch. It's out of scope for this PR, so I filed it as #456" |
7.2 Don't defend — understand
Take a review comment as an attack and your response turns defensive. A defensive response wears the reviewer down and makes them not want to look closely at that author's PR next time.
Even when a comment seems wrong, the first reaction is "why did the reviewer see it this way?" If the reviewer misunderstood, the misunderstanding itself is information — it means the code is unclear enough to be misread that way. Fix the code to be clear, or add a comment.
7.3 How to stack review-response commits
On a PR where review has already started, applying changes as new commits is the default. Adding a commit like address review feedback lets the reviewer look separately at "what changed since the first review." They don't have to re-read parts they've already seen.
Right before merge, you can squash these commits per the team convention. The key is to preserve history while review is in progress and clean up at merge time. Rewrite history with a force-push mid-review and the reviewer loses the context of their own comments.
Chapter 8 · Draft PRs and signaling readiness
A PR has two states — "don't look yet" and "please look now." Fail to signal these clearly and the reviewer's time is wasted.
8.1 What draft PRs are for
A Draft PR is an explicit signal that "the code is up, but this is not a review request yet." Use it when:
- You want to run CI early — just check the build and test results first
- You want early feedback on direction — explicitly say "don't review the whole thing, just whether this approach is right"
- You want to show the team work is in progress — to prevent duplicate work
The core of a draft is managing the reviewer's expectations. Write "just review the direction" on a draft, and the reviewer won't point out variable names or missing tests — that's not the stage to look at yet.
8.2 Signal "ready" clearly
Converting a draft to "Ready for review" is itself a signal. But sometimes that alone isn't enough.
- If you applied early feedback from the draft, leave a comment on what you changed.
- When requesting review, be clear about who you're requesting from — "anyone" usually becomes "no one."
- If the PR is blocking another PR or a deployment, write down that urgency.
8.3 Don't leave drafts to rot
An old draft PR is noise. A draft stuck for days makes the team unsure "is this work alive or dead?" Keep a draft open only while it's active work; if it's stopped, close it or leave a status comment.
Chapter 9 · Authoring PRs in the AI era
In 2026 the landscape of PR authoring has changed. A large share of the code is generated by AI agents, and AI can draft the PR description too. But the core principles haven't changed — if anything, they matter more.
9.1 What AI is good at: drafting descriptions and self-review
AI is excellent at specific parts of PR authoring.
- Drafting the PR description — give it the diff as input and it summarizes "what and how." But "why" is known only to a human — the author must rewrite the "why" field of an AI draft.
- Assisting self-review — "find the left-behind debug code, missing error handling, and untested branches in this diff" is a task AI does well. You can run the Chapter 6 checklist through AI as a first pass.
- Cleaning up commit messages — it can draft messages when restructuring into logical commits.
9.2 What AI can't do: accountability and context
There are things AI can never substitute for.
- The real context of "why" — what business or technical decision this change came out of lives only in a human's head.
- Accountability for unreviewed output — code put up in a PR, whether AI wrote it or a human did, is code the author vouches for.
9.3 Never do this: dumping unreviewed agent output
The most harmful anti-pattern of the AI era is this — putting up agent-generated code as a PR without the author even reading it.
This is equivalent to telling the reviewer, "please look at code I haven't looked at." The social contract of review breaks. The reviewer assumes they're reviewing "code the author has examined once," and when that assumption becomes false, the reviewer ends up shouldering the first-pass review the author should have done.
The rule: AI-generated code has to pass the exact same PR bar as human-written code. Cut it small, carry one idea, run a self-review as the author, and have the author understand and vouch for every line. AI only makes the diff faster — the responsibility to make it reviewable still belongs to the author.
9.4 Why small PRs matter more in the AI era
AI can produce code fast and in large volume. This makes the small-PR principle matter more — the faster generation gets, the faster the reviewer drowns without the discipline to cut it into reviewable units.
AI raised authoring speed; it did not raise review bandwidth. Closing that gap is the author's PR-authoring craft.
Epilogue — A good PR is a gift to the reviewer
A PR's review latency is controlled by the author. The reviewer isn't slow — the unreviewable PR is slow. Cut it small, carry one idea, write the "why" and "verification" in the description, structure the diff to be read, and run a self-review before pushing — and the same reviewer approves the same code several times faster.
This is a favor for the reviewer and, in the end, a thing you do for yourself. Fast review is fast merge, fast merge is fast feedback, and fast feedback catches the wrong direction early. The craft of writing a good PR is a collaboration skill and, at the same time, a skill for keeping your own work cycle short.
The reviewable-PR checklist
- Size — the diff is below the cognitive cliff (roughly under 200–400 lines)
- One idea — you did not mix a refactor with a behavior change
- What — the PR description summarizes the change in one or two sentences
- Why — the reason this change is needed is written (if an AI draft, a human rewrote it)
- Verification — there are check steps the reviewer can follow
- Out of scope — what is not being done this time is stated
- Visuals — if it's a UI change, screenshots are attached
- Commit hygiene — commits are logical units, and formatting is separated
- Self-review — you looked at "Files changed" line by line and there are no debug traces
- Signal — the draft/ready state and the review target are clear
Anti-patterns to avoid
- The giant PR — 800 lines with a one-line "add feature." An LGTM is a surrender, not a review
- The mixed PR — refactor + behavior change + typo in one diff. The signal drowns in noise
- "While I'm at it" — don't slip in unrelated changes. Make them a separate PR/ticket
- The empty description — don't toss the diff with no "why" and no "verification"
- Formatting contamination — don't mix auto-formatter output into the same commit as a behavior change
- Defensive responses — don't take a review comment as an attack. A misunderstanding is information that the code is unclear
- Force-push mid-review — don't rewrite history while review is in progress
- Agent output dump — don't put up AI code you haven't read as a PR
Next post teaser
The next post is "The Reviewer's Craft — How to Do Fast, Kind, and Strict Code Review." If this post was the craft of the side that writes a PR, the next is the craft of the side that reads one. What to look at first, which comments help and which are noise, where to set the baseline for approval versus change request — and how to divide the roles of an AI review bot and a human reviewer.
The ten minutes spent writing a good PR prevents the half-day of a single review round trip. Those ten minutes are a gift to the reviewer and an investment sent to your future self.
현재 단락 (1/196)
"Why does my PR always take so long to merge?" Many engineers look for the answer in the reviewer: t...