1. “리뷰해줘”가 일반론만 뱉는 이유
AI에게 diff를 던지고 “리뷰해줘”라고 하면 보통 이런 게 돌아옵니다.- 에러 처리가 부족합니다
- 변수명을 더 명확하게 하면 좋겠습니다
- 테스트 코드를 추가하는 것이 좋습니다
- 매직 넘버를 상수로 추출하세요
2. AI 코드리뷰 리뷰어를 세 명으로 나눴습니다
제가 쓴 구성은 이렇습니다. 세 명을 동시에 띄우고 각자 다른 것만 보게 했습니다.
| 리뷰어 | 보는 것 | 안 보는 것 |
|---|---|---|
| A. 동작 정합성 | 런타임에 실제로 깨지는가. 선언 순서, 0 나눗셈, 제거한 심볼의 잔존 참조, 예외 경로 | 스타일, 문서, 규칙 준수 |
| B. 규칙 준수 | 프로젝트 표준을 지켰는가. 금지된 패턴, 필수 규격, 다른 모듈과의 편차 | 코드 버그 사냥 |
| C. 주장 반증 | 저자가 PR 본문에 쓴 주장이 코드와 일치하는가. 수치가 최신인가 | 위 두 개 |
브리프에 반드시 넣은 4가지
리뷰어에게 주는 지시문에 아래를 매번 넣습니다. 이게 없으면 결과가 쓰레기가 됩니다. ① 렌즈 고정, “이 관점만 봐라. 다른 축은 다른 사람이 본다”고 명시합니다. 안 적으면 셋 다 같은 일반론으로 수렴합니다. ② 심각도 부풀리기 금지Critical/High는 "실제로 터지는 시나리오 + 그때 무엇이 깨지는가"를
구체적으로 못 쓰면 Medium 이하로 내려라.
AI는 심각도를 과장하는 경향이 뚜렷합니다. 전부 High로 올려놓으면 우선순위가 사라져서 리뷰가 무용해집니다.
③ 근거 없으면 “미확인”으로 표기
코드 라인으로 반증 불가능한 것만 올려라.
확신 못 하면 심각도를 낮추고 "미확인"이라고 적어라.
이 한 줄이 환각을 크게 줄입니다. “모르겠다”는 출구를 열어주지 않으면 AI는 빈칸을 그럴듯한 문장으로 채웁니다.
④ 지적 전에 근거 문서가 실재하는지 확인
표준 위반을 지적하기 전에 그 조항이 실제로 문서에 있는지 먼저 확인하라.
없으면 "문서에 조항 부재 → 지적 불가"라고 적어라.
이건 제가 실제로 당해서 넣은 항목입니다. “표준 위반이니 걷어내라”고 지적했는데 정작 그 표준 문서에 해당 조항이 없었습니다. 저자가 문서를 근거로 반박할 수 있는 상태였고, 결국 문서를 먼저 고쳐야 했습니다.
3. 실제로 나온 것
세 명의 결과를 받아서 제가 직접 코드로 재확인했습니다. 최종적으로 남은 건 4건인데, 가장 큰 것 하나만 소개하겠습니다. 코드에 이런 판정 함수가 있었습니다.안전한가? = (허용목록에 있다) OR (이름이 특정 형식이다)
그리고 바로 위 주석에 저자가 이렇게 적어놨습니다.
이 판정이 틀리면 대가는 “대상 하나를 빠뜨림” 정도라 감수할 수 있다방향이 반대였습니다.
OR이니까 이건 배제 조건이 아니라 포함 조건입니다. 실제 사용자가 우연히 그 형식의 이름을 쓰면 “빠지는” 게 아니라 들어옵니다. 그러면 실사용자 데이터가 오염되고, 이건 되돌릴 수 없는 종류였습니다.
핵심은 저자가 리스크를 인지하고 있었다는 점입니다. 주석에 “이름은 사용자 자유 입력이라 겹칠 수 있다”고까지 적었습니다. 그런데 그 다음 문장에서 비용을 반대로 계산했습니다.
이게 리뷰에서 놓치기 가장 쉬운 유형입니다. 주석이 있으면 “검토했구나” 하고 넘어갑니다. 주석의 존재가 검토의 증거처럼 읽히는데, 실제로는 잘못된 결론이 적혀 있을 수 있습니다.
저자는 이 지적을 받고 판정식을 앵커링된 정규식으로 좁히고, 환경변수로 덮어쓸 수 있던 구멍까지 스스로 막았습니다. 제가 요청한 건 앞의 절반뿐이었습니다.
그리고 제 지적 하나가 틀렸습니다
같은 작업에서 설정 파일 12개를 훑어 “전부 같은 결함이 있다”고 보고했습니다. 그런데 3개는 이미 고쳐져 있었습니다. 제 패턴이 이 형태를 전제했습니다.도구 서브커맨드
실제는 이랬습니다.
도구 --설정="..." 서브커맨드
명령어와 서브커맨드 사이에 전역 옵션이 낀 형태입니다. git --no-pager log, docker --context=prod ps 처럼 흔한 구조인데, 패턴을 짤 때 짧은 형태만 떠올렸습니다.
무서운 건 결과가 0 / 12로 아주 깔끔하게 나왔다는 점입니다. 결함이 없어서 0인지, 못 찾아서 0인지 구분이 안 되는데 표로 정리되면 신뢰가 붙습니다. 그리고 이미 고쳐진 그 3개에 정답 구현이 들어 있었습니다. 못 봤으면 있는 걸 새로 짤 뻔했습니다.
4. AI 코드리뷰가 안 되는 지점
방법이 통했다고 해서 사람이 빠질 수 있다는 뜻은 아닙니다. 같은 세션에서 두 가지가 걸렸습니다. 리뷰어 한 명이 아무 말 없이 끝났습니다. 세 명 중 하나가 작업이 끝났다는 신호만 보내고 결과를 안 냈습니다. 세 번 요청했는데 끝까지 안 왔고, 결국 그 담당 범위는 제가 직접 계산했습니다. 위임한 일이 조용히 사라지는 경우가 실제로 있습니다. 다른 한 명의 보고에 숫자 오류가 있었습니다. 특정 값을 “202”로 보고했는데, 제가 다시 세어보니 계산이 맞지 않았습니다. 산문 속 숫자는 diff 검토로는 안 걸립니다. 문장이 자연스러우면 그냥 통과합니다. 그래서 순서가 이렇게 됩니다.1. 세 명 병렬로 리뷰 → 각자 발견 목록
2. 사람이 직접 코드로 재확인 ← 생략 불가
3. 심각도 확정 및 머지 차단 여부 판정
4. 게시
2번을 빼면 1번의 가치가 대부분 사라집니다. AI 코드리뷰는 “볼 곳을 좁혀주는 장치”이지 판정 주체가 아닙니다. 저는 머지를 막을 만한 지적만 직접 열어서 확인했고, 실제로 그 과정에서 위의 제 오판이 잡혔습니다.
5. 정리
- AI 코드리뷰에 “잘 봐줘”라고 하면 어떤 코드에도 해당되는 평균값이 나옵니다. 관점을 쪼개야 깊이가 생깁니다
- 최소 구성은 동작 정합성 / 규칙 준수 / 주장 반증 3개입니다. 특히 앞의 두 개는 반드시 분리하세요. 한 명이 둘을 다 보면 “동작하니까 통과”로 끝납니다
- 브리프에 심각도 부풀리기 금지 + 근거 없으면 미확인 표기를 넣으세요. 두 줄로 환각이 크게 줄어듭니다
- 표준 위반을 지적하기 전에 그 조항이 문서에 실재하는지 확인하세요. 없으면 문서를 고치는 게 먼저입니다
- 주석이 있다고 검토된 게 아닙니다. 주석의 결론이 코드와 같은 방향인지 따로 봐야 합니다
- 리뷰어 보고는 조용히 사라지거나 숫자가 틀릴 수 있습니다. 머지를 막을 지적만이라도 사람이 직접 열어보세요
이런 실측 기록은 Threads @wonizz.ai 에 먼저 올립니다. 에이전트에게 맡긴 일에서 무엇이 깨졌는지, 글로 정리하기 전의 기록을 거기서 먼저 보실 수 있습니다.
함께 보면 좋은 글
AI 가 만든 결과를 어떻게 확인할지 더 보고 싶다면 아래 세 편을 이어서 읽어 보세요.
- 코드 리뷰가 못 따라갈 때 검사기 3가지 함정: 검사기가 통과를 내면서 정작 그 항목을 보지 않던 자리
- 바이브 코딩 현실, 테스트는 통과했는데 261편 중 6편만 맞았다: 셀프테스트가 초록인데 실데이터에서 틀린 세 번
- AI 코딩 도구, 빨라진 건 맞는데 어디가 느려졌을까: 연구 결과가 엇갈리는 이유와 느려진 자리
AI 코드리뷰를 쓰고 계시다면 한 번에 맡기시나요, 관점을 나눠 맡기시나요? 댓글로 알려 주세요. 모든 댓글을 읽고 답합니다.
세 관점으로 나누어보는 아이디어는 정말 유효한 것 같아요! 단순히 모델 성능만 보지 않고 다양한 의견을 반영하는 것이 코드 리뷰의 질을 높이는 데 훨씬 도움이 될 것 같습니다.
읽어주셔서 감사합니다.
덧붙이자면, 세 관점 중 실제로 가장 값이 컸던 건 A(동작 정합성)와 B(규칙 준수)를 분리한 것이었습니다. 한 명에게 둘을 같이 맡기면 거의 항상 “동작하니까 통과”로 끝나더군요. 사람이 리뷰할 때도 같은 함정이 있는 것 같습니다.
반대로 C(주장 반증)는 기대만큼은 아니었습니다. PR 본문에 주장이 별로 없는 변경에서는 할 일이 없어서요. 변경 설명이 긴 PR에서만 켜는 게 나아 보입니다.
후속 기록 (2026-08-14)
글에 쓴 “설정 파일 12개를 훑어 전부 결함이라고 보고했는데 3개는 이미 고쳐져 있었다” 건의 뒷이야기입니다.
같은 날 그 3개를 고치려다 저장이 5번 연속 실패했습니다. 캐시 문제인 줄 알고 캐시를 지웠고, 유료 기능 제한인가 싶어 값을 단순화해봤고, 폼 제출 방식도 세 가지로 바꿔봤습니다. 전부 아니었습니다.
원인은
document.querySelector('input[name=ai_save]')가 잡는 첫 번째 저장 버튼이 폼 밖에 있었던 것이었습니다. 폼 밖에 있는type=submit버튼은 클릭해도 아무 일도 일어나지 않습니다. 같은 name 의 버튼이 폼 안에도 여러 개 있었고, 그중 하나를 누르니 바로 저장됐습니다.결정적이었던 건 무해한 필드(이름)를 바꿔서 저장이 되는지부터 확인한 것이었습니다. 그것도 안 되는 걸 보고서야 “특정 필드 문제”가 아니라 “저장 경로 문제”라는 걸 알았습니다.
본문의 교훈(“0건이 결함 없음인지 못 찾은 건지 구분되지 않는다”)과 같은 계열입니다. querySelector 의 첫 매칭이 옳다고 가정하지 말 것 하나를 더 배웠습니다.