처음 코드 리뷰를 받던 날을 아직도 기억한다. PR 올리고 나서 리뷰 알림이 오면 심장이 쿵 내려앉던 그 느낌. "내 코드에 뭔가 문제가 있는 걸까", "이것도 모른다고 무시하면 어쩌지", "코멘트가 왜 이렇게 많아"...
근데 시간이 지나고 보니 코드 리뷰는 공격이 아니라 협업이었다. 리뷰어도 처음엔 나처럼 긴장했을 거고, 모두가 더 나은 코드를 만들려는 같은 목표를 갖고 있다. 이 글은 그 사실을 좀 더 빨리 알았더라면 하는 아쉬움에서 시작했다.
리뷰이로서, 그리고 나중에 리뷰어로서 배운 것들을 정리해봤다.
1부: 리뷰이 편 — PR을 잘 올리는 법
PR은 편지다
코드를 올리는 게 아니라 동료에게 편지를 쓰는 거라고 생각해보자. 맥락 없이 코드 덩어리만 던지면 리뷰어는 "이게 뭐지?"부터 시작해야 한다. 반대로 친절한 PR description이 있으면 리뷰 시간이 절반 이하로 줄어든다.
좋은 PR description에는 이런 게 들어가야 한다:
## 변경 사항 요약
사용자 프로필 이미지 업로드 기능 추가
## 왜 이렇게 만들었나
- Cloudinary 대신 S3를 선택한 이유: 이미 AWS 인프라를 쓰고 있어서 비용 절감
- 파일 크기 제한을 5MB로 설정한 이유: 평균 프로필 이미지가 2MB 내외
## 테스트 방법
1. 로그인 후 /profile 페이지 접속
2. 이미지 업로드 버튼 클릭
3. 5MB 이하 이미지 선택 → 성공 메시지 확인
4. 5MB 초과 이미지 선택 → 에러 메시지 확인
## 스크린샷
[업로드 전] / [업로드 후]
## 관련 이슈
Closes #123
이게 귀찮아 보이지만, 리뷰어 입장에서는 이런 PR이 오면 바로 리뷰 집중할 수 있어서 훨씬 빠르게 approve가 난다.
PR 크기는 작을수록 좋다
대형마트에서 한 번에 100가지 물건을 계산하는 거랑, 편의점에서 5가지 물건 계산하는 거랑 어느 게 더 빠를까? 코드 리뷰도 마찬가지다.
500줄짜리 PR을 리뷰하는 건 지독한 일이다. 집중력이 분산되고, 문제를 놓치기 쉽고, 리뷰어가 그냥 LGTM(Looks Good To Me) 누르고 싶은 유혹을 느낀다.
실무에서 통하는 PR 크기 기준:
| PR 크기 | 변경 줄 수 | 리뷰 소요 시간 | 권장 여부 |
|---|---|---|---|
| 소 | ~100줄 | 10~20분 | 최고 |
| 중 | 100~300줄 | 30~60분 | 괜찮음 |
| 대 | 300~500줄 | 1~2시간 | 주의 |
| 초대형 | 500줄 이상 | 반나절+ | 지양 |
기능이 크다면 PR을 쪼개는 게 맞다. "리팩토링 PR", "인터페이스 변경 PR", "구현 PR"로 나누면 각각 리뷰하기 훨씬 편해진다.
셀프 리뷰를 먼저 해라
PR 올리기 전에 내가 먼저 리뷰어 눈으로 한 번 읽어봐라. GitHub에서 diff 보면서 이런 걸 체크해보자:
// 이런 코드 올리기 전에 스스로 물어봐라:
// 1. 변수명이 의미 있는가?
const d = new Date(); // 나쁨
const createdAt = new Date(); // 좋음
// 2. 함수가 하나의 일만 하는가?
function processUserAndSendEmail(user: User) {
// 사용자 처리 + 이메일 발송을 동시에?
// → 두 함수로 나누는 게 맞다
}
// 3. 에러 케이스를 처리했는가?
async function fetchUser(id: string) {
const user = await db.users.findById(id);
return user; // user가 null이면?
}
// 4. 테스트가 있는가?
// 5. console.log 같은 디버그 코드가 남아있는가?
셀프 리뷰로 명백한 실수들을 미리 잡으면, 리뷰어는 진짜 중요한 로직 문제에 집중할 수 있다.
코멘트를 방어적으로 받지 마라
리뷰 코멘트가 달리면 처음엔 방어적이 된다. "이게 왜 문제야", "내 의도를 모르네" 같은 감정이 올라온다. 이건 자연스러운 반응이지만, 잠깐 멈추는 게 중요하다.
코멘트를 받았을 때 좋은 반응 패턴:
리뷰어: "이 함수는 너무 많은 일을 하는 것 같아요. SRP 위반인 것 같습니다."
나쁜 반응:
"이 정도면 괜찮지 않나요? 잘 동작하는데요."
좋은 반응:
"맞아요, 확인해보니 파일 파싱과 DB 저장을 같이 하고 있네요.
분리하는 게 맞겠습니다. 수정해볼게요!"
또는:
"왜 이 부분이 SRP 위반인지 조금 더 설명해주실 수 있나요?
저는 관련 기능이라 묶어도 된다고 생각했는데, 제가 놓친 게 있는 것 같아서요."
두 번째 반응처럼 이해가 안 되면 질문하는 게 훨씬 낫다. 리뷰어는 니 코드를 공격하는 게 아니라, 같이 더 나은 코드를 만들려는 거다.
2부: 리뷰어 편 — 건설적인 피드백 주는 법
코드를 리뷰하고 사람을 리뷰하지 마라
코드 리뷰에서 가장 흔한 실수는 코드가 아닌 사람을 비판하는 것이다.
사람 비판 (나쁨):
"왜 이렇게 복잡하게 짰어요? 이해가 안 되는데..."
"이건 기초도 모르는 것 같네요."
"제가 짰으면 이렇게 안 했을 텐데."
코드 비판 (좋음):
"이 로직이 조금 복잡해서 따라가기 어렵네요.
중간 변수를 하나 추출하면 더 읽기 쉬울 것 같아요."
"이 패턴보다 팩토리 함수를 쓰면 테스트하기 더 편할 것 같아요."
"더 나은 방법이 있을 것 같아서요 - 이 부분을 이렇게 바꾸면 어떨까요?"
피드백은 항상 코드에 관한 것이어야 한다. "당신이 잘못했다"가 아니라 "이 코드를 이렇게 바꾸면 어떨까"로.
피드백의 중요도를 표시해라
모든 코멘트가 동일한 무게를 가지면 리뷰이는 뭘 먼저 고쳐야 하는지 모른다. prefix를 붙여서 구분하는 게 도움이 된다:
[BLOCKING] - 이건 반드시 고쳐야 머지 가능
"[BLOCKING] SQL 쿼리에 사용자 입력을 직접 넣고 있어요.
SQL 인젝션 취약점이 생길 수 있습니다. prepared statement 써야 해요."
[NIT] - 작은 스타일 문제, 선택사항
"[NIT] 변수명을 `data`보다 `userData`로 하면 더 명확할 것 같아요."
[QUESTION] - 궁금한 점, 비판 아님
"[QUESTION] 여기서 왜 setTimeout을 쓰셨나요?
다른 방법도 있는 것 같아서 궁금했어요."
[SUGGESTION] - 개선 제안, 필수 아님
"[SUGGESTION] 이 부분을 커스텀 훅으로 분리하면 재사용하기 좋을 것 같아요."
이렇게 하면 리뷰이가 [BLOCKING] 먼저 처리하고, [NIT]는 시간 남을 때 볼 수 있다.
칭찬도 코드 리뷰다
리뷰가 항상 문제 찾기일 필요는 없다. 좋은 코드를 발견했을 때 칭찬하는 것도 리뷰의 일부다.
"이 에러 처리 방식 정말 깔끔하네요! 저도 이렇게 써봐야겠어요."
"이 함수 이름 직관적이어서 별도 설명 없이도 바로 이해됐어요."
"테스트 케이스를 이렇게 꼼꼼하게 작성해주셔서 나중에 유지보수할 때
도움이 많이 될 것 같아요."
팀 문화에 긍정적인 리뷰 습관을 들이면, 부정적인 피드백을 줄 때도 더 잘 받아들여진다.
3부: 리뷰 체크리스트
리뷰이용 PR 올리기 전 체크리스트
## Before Opening PR
### 코드 품질
- [ ] 셀프 리뷰 완료 (diff 보면서 읽기)
- [ ] console.log, debugger 같은 디버그 코드 제거
- [ ] 주석이 "왜"를 설명하는가 (무엇이 아니라)
- [ ] 함수/변수명이 의도를 표현하는가
- [ ] 중복 코드 없음
### 기능
- [ ] 에러 케이스 처리됨
- [ ] 엣지 케이스 고려됨
- [ ] 성능 이슈 없음 (N+1 쿼리, 메모리 누수 등)
### 테스트
- [ ] 새 기능에 테스트 추가됨
- [ ] 기존 테스트 모두 통과
- [ ] 테스트가 실제로 의미 있는 것을 테스트하는가
### PR 자체
- [ ] PR description이 충분히 설명적인가
- [ ] PR 크기가 리뷰하기 적당한가 (300줄 이하 권장)
- [ ] 스크린샷 또는 데모 추가 (UI 변경의 경우)
- [ ] 관련 이슈 연결됨
리뷰어용 리뷰 체크리스트
## Code Review Checklist
### 보안
- [ ] SQL 인젝션 취약점
- [ ] XSS 가능성
- [ ] 민감한 정보 노출 (API 키, 패스워드 하드코딩)
- [ ] 적절한 인증/권한 검사
### 로직
- [ ] 비즈니스 로직이 요구사항과 일치하는가
- [ ] 에러 처리가 충분한가
- [ ] 엣지 케이스를 다루는가
### 설계
- [ ] 단일 책임 원칙 위반 없음
- [ ] 적절한 추상화 수준
- [ ] 향후 변경 시 영향 범위가 제한적인가
### 성능
- [ ] 불필요한 연산 없음
- [ ] 적절한 캐싱 사용
- [ ] 데이터베이스 쿼리 효율성
### 테스트
- [ ] 중요한 케이스가 테스트됨
- [ ] 테스트가 실제로 실패할 수 있는가
- [ ] 테스트가 빠르고 결정론적인가
4부: 실전 시나리오
시나리오 1: 닌자 코드를 만났을 때
// 리뷰어가 이런 코드를 받았을 때
const r = u.map(x => x.a > 0 ? {...x, b: x.b * 1.1} : x)
.filter(y => y.c)
.reduce((acc, z) => acc + z.b, 0);
닌자 코드(변수명이 a, b, c, x, y, z인 코드)를 만나면 어떻게 리뷰해야 할까?
좋은 리뷰 코멘트:
이 코드가 무엇을 하는지 파악하는 데 시간이 걸렸어요.
변수명을 더 의미 있게 바꾸면 유지보수가 쉬워질 것 같아요.
예를 들어 이렇게:
const totalActiveRevenue = users
.map(user => user.isEligible
? { ...user, revenue: user.revenue * 1.1 }
: user
)
.filter(user => user.isActive)
.reduce((total, user) => total + user.revenue, 0);
변수명만 바꿔도 코멘트 없이도 읽히네요!
시나리오 2: 성능 문제를 발견했을 때
// 리뷰 중 발견한 N+1 쿼리
async function getPostsWithAuthors(postIds: string[]) {
const posts = await db.posts.findMany({ where: { id: { in: postIds } } });
// 문제! 각 포스트마다 별도 쿼리 실행
const postsWithAuthors = await Promise.all(
posts.map(async (post) => {
const author = await db.users.findById(post.authorId); // N번 실행!
return { ...post, author };
})
);
return postsWithAuthors;
}
리뷰 코멘트:
[BLOCKING] 이 부분에서 N+1 쿼리 문제가 있어요.
포스트가 100개면 DB 쿼리가 101번 실행됩니다.
include를 써서 한 번에 가져오는 게 어떨까요?
const posts = await db.posts.findMany({
where: { id: { in: postIds } },
include: { author: true } // 쿼리 1번으로 해결!
});
또는 authorId를 모아서 한 번에 조회하는 것도 방법이에요.
시나리오 3: 닌지라 코멘트를 받았을 때
리뷰어가 이런 코멘트를 달았다고 하자:
"이거 좀 이상한데요."
이상하다는 게 뭔지, 어떻게 고쳐야 하는지 전혀 모르는 상황. 이럴 때:
리뷰이:
"조금 더 자세히 설명해주실 수 있을까요? 어떤 부분이 이상한지,
그리고 어떻게 바꾸면 좋을지 알 수 있으면 수정하는 데 도움이 될 것 같아요 :)"
리뷰어에게 더 구체적인 설명을 요청하는 건 전혀 이상한 게 아니다. 오히려 그래야 한다.
5부: 비동기 리뷰 문화 만들기
리뷰 SLA를 팀에서 정해라
리뷰 요청이 며칠째 방치되면 개발 흐름이 끊긴다. 팀에서 미리 SLA(Service Level Agreement)를 정해두면 좋다:
팀 리뷰 SLA 예시:
- 소규모 PR (100줄 이하): 24시간 내 1차 리뷰
- 일반 PR (100~300줄): 48시간 내 1차 리뷰
- 대형 PR (300줄 이상): 72시간 내 1차 리뷰
- 긴급 핫픽스: 2시간 내 리뷰
리뷰어 부재 시:
- 백업 리뷰어 지정
- 스탠드업에서 블로킹 PR 공유
리뷰 코멘트의 톤을 팀에서 통일해라
팀마다 리뷰 문화가 다르다. 어떤 팀은 직설적이고, 어떤 팀은 부드럽다. 어떤 스타일이든 팀 안에서 일관되게 유지하는 게 중요하다.
팀 컨벤션 예시:
## 코드 리뷰 컨벤션
### 코멘트 prefix
- [BLOCKING]: 머지 전 반드시 수정
- [NIT]: 소소한 스타일, 선택사항
- [QUESTION]: 질문, 비판 아님
- [SUGGESTION]: 개선 제안, 선택사항
- [PRAISE]: 좋은 코드 칭찬
### 기본 규칙
1. 코드를 비판하고 사람을 비판하지 않는다
2. 이유 없는 "바꿔요"는 하지 않는다 (왜 바꿔야 하는지 설명)
3. 수정 제안 코드를 같이 제시한다 (가능하면)
4. 칭찬도 적극적으로 한다
마무리: 코드 리뷰는 팀 스포츠다
코드 리뷰가 두렵고 불편한 건 자연스러운 일이다. 내가 만든 것을 남이 평가한다는 게 편할 리 없다. 근데 시간이 지나면서 리뷰 문화가 팀을 얼마나 빠르게 성장시키는지 느끼게 된다.
좋은 리뷰 문화를 가진 팀에서 일하면:
- 버그가 프로덕션에 나가기 전에 잡힌다
- 팀원 간에 지식이 자연스럽게 공유된다
- 주니어는 시니어의 사고방식을 배운다
- 코드베이스가 일관된 품질을 유지한다
처음엔 리뷰 받는 게 두렵고, 리뷰 주는 게 어색하다. 근데 꾸준히 하다 보면 팀 전체의 실력이 올라가는 걸 느낀다. 그게 코드 리뷰의 진짜 가치다.