배경
한 프론트엔드 저장소에 AI 코드리뷰를 두 겹으로 붙였다. PR에 자동으로 달리는 Gemini 리뷰 봇, 그리고 작업 중간에 명시적으로 태우는 code-review 패스다.
목표는 “지적을 받는 것” 자체가 아니라, 지적사항을 실제 커밋으로 환류시키는 것이었다. 두 달 정도 돌려보니 봇이 잘 잡는 클래스와 전혀 못 잡는 클래스가 꽤 뚜렷하게 갈렸다.
참고로 이 저장소는 해당 기간 커밋 1708건 중 1212건(71%)에 Co-Authored-By: Claude 서명이 남아 있다. 모델 세대도 커밋 메시지에 그대로 기록돼 있어서 Sonnet 4.6 → Opus 4.8 → Opus 4.8 1M → Opus 5 순으로 바뀐 흔적을 볼 수 있다.
봇이 잡은 것
실제로 커밋으로 이어진 지적들을 유형별로 정리하면 이렇다.
| 유형 | 지적 내용 |
|---|---|
| 관용구 위반 | 차트 option 객체를 매 렌더 재생성 (useMemo 누락) |
| 누락된 cleanup | 메모리 누수 3건 추가 발견 |
| 레이스 컨디션 | 민감도 분석 로딩 순서 |
| 가드 비대칭 | heatmap의 x축은 가드가 있는데 y축만 있음 |
| 중복 방어 | escapeHtml이 이미 정규화하는데 바깥에 String() 래퍼 |
| 부작용 혼입 | setGridRows 업데이터 함수 안에서 다른 setState 호출 |
| 도달 불가능한 분기 | crumbOf의 구분선 처리 |
몇 개는 조금 더 설명할 만하다.
업데이터 안에 부작용이 섞여 있었다
setGridRows((prev) => { ... setDeleteIdList(...); return next; }) 형태였다. React의 업데이터 함수는 순수해야 하는데 안에서 다른 상태를 갱신하고 있었다. Strict Mode나 동시성 렌더에서 업데이터가 두 번 호출되면 deleteIdList에 같은 항목이 중복으로 들어간다.
수정은 부작용을 이벤트 핸들러 쪽으로 끌어올려 현재 스코프의 gridRows를 직접 참조해 계산하고, 두 상태를 순차로 갱신하는 방식이었다. 실제로 버그로 신고된 적은 없었지만 조건이 맞으면 터질 자리였다.
가드의 비대칭
heatmap에서 yCats[y]에는 존재 확인 가드가 있는데 MODULES[x]에는 없었다. 봇이 “왜 한쪽만 있냐”고 지적했다. 이런 대칭성 위반은 사람이 diff를 볼 때 잘 안 보이는데, 패턴 매칭으로는 잘 걸린다.
상태 미러링
useState + useEffect로 location.hash를 activeId에 복사해두고 쓰는 코드가 있었다. 이건 desync 창(hash는 바뀌었는데 state는 아직 안 바뀐 순간)을 만든다. 렌더 시점에 hash에서 직접 파생하도록 바꿔 미러링과 effect를 함께 제거했다.
봇이 못 잡은 것
가장 큰 성능 문제는 봇이 하나도 못 잡았다.
PDF 다운로드가 7초 걸리던 문제가 그렇다. 코드만 보면 어디에도 이상한 구석이 없다. react-pdf로 PDF를 만드는 지극히 평범한 코드다. 병목이 한글 폰트의 텍스트 layout이라는 건 chrome-devtools로 직접 계측해서 20여 종의 조합을 실험한 끝에 나왔다. 정적 분석으로는 도달할 수 없는 결론이다.
봇이 잘 잡는 것과 못 잡는 것
두 달을 돌리고 나서 경계가 이렇게 정리됐다.
| 잘 잡는 클래스 | 못 잡는 클래스 |
|---|---|
| 관용구 위반 (useMemo·useCallback 누락) | 성능 병목 (실측이 필요한 것) |
| 누락된 cleanup (dispose·revoke·clearTimeout) | 도메인 정합성 (이 계산식이 업무적으로 맞는가) |
| exhaustive check·가드 대칭성 | 아키텍처 수준의 중복 (V1/V2 이중 스택 같은 것) |
| 도달 불가능한 분기 | 사용자에게 실제로 어떻게 보이는가 |
공통점이 있다. 봇이 잘 잡는 건 파일 하나 안에서 판정 가능한 것이고, 못 잡는 건 실행해봐야 알거나 도메인 지식이 필요한 것이다.
남는 교훈
AI 리뷰 봇을 붙이면 지적사항 수는 확실히 늘어난다. 위 표의 왼쪽 열은 사람 리뷰어가 꾸준히 잡기 어려운 종류이기도 하다 — 지루하고, 놓쳐도 당장 티가 안 나기 때문이다. 그런 걸 기계가 대신 봐주는 건 실질적인 이득이다.
다만 봇을 붙였다고 리뷰가 끝났다고 착각하면 위험하다. 오른쪽 열은 여전히 사람 몫이고, 그중에서도 성능 문제는 계측 없이는 누구도 알 수 없다. 봇의 통과 여부를 “문제 없음”의 근거로 쓰지 않는 것 — 그게 두 달 동안 얻은 가장 실용적인 결론이다.