AI 리뷰어가 해결할 수 있는 버그보다 더 많은 버그를 발견했을 때: AI 코드 리뷰 결과를 위한 트리아지 파이프라인 구축
요약
AI 리뷰 도구가 너무 많은 발견 목록을 생성하여 오히려 개발 흐름을 방해하는 문제를 다룹니다. 이 글은 AI가 보고한 수많은 버그 중, 실제로 병합을 막아야 할 핵심적인 문제들만 선별하는 '트리아지 파이프라인' 구축 방법을 제시합니다.
핵심 포인트
- AI 리뷰어의 문제는 정확도보다 양(Volume)에 있습니다.
- PR 변경 라인에 국한된 발견 목록만 필터링해야 합니다.
- 동일 이슈를 반복 보고하지 않도록 지문(Fingerprint) 확보가 중요합니다.
- 최대 코멘트 개수 제한 및 심각도 순 정렬로 노이즈를 줄여야 합니다.
이번 주 Hacker News에서 상당히 생각할 거리를 던지는 글을 접했습니다. Postgres 19의 출시 일정이 연기되었는데, 그 이유가 AI 리뷰 도구들이 사람이 처리할 수 있는 버그보다 더 많은 버그를 발견했기 때문이라고 합니다. 들어보면 좋은 소식처럼 들립니다. 왜냐하면 버그를 빨리 찾는 것이 항상 저렴하니까요. 하지만 팀 레포지토리에 AI 리뷰어를 도입해 본 사람이라면 그 이면의 어려움을 알고 있습니다. 약 일주일이 지나자, PR당 30개의 코멘트가 추가되었고, 그중 절반은 이미 언급된 내용이고, 3분의 1은 오탐(false positive)이었으며, 개발자들은 내용을 읽어보지도 않고 'Resolve' 버튼을 연달아 누르기 시작했습니다. 이때 AI 리뷰는 더 이상 유용한 도구가 아니라 단순한 소음이 되어버립니다. 이 글에서는 제가 혼란스러운 발견 목록(finding)들을 팀이 처리할 수 있는 짧은 목록으로 변환하기 위해 간단한 **트리아지 파이프라인(triage pipeline)**을 구축한 방법을 공유합니다.
진짜 문제는 정확도가 아니라, 양이다
대부분의 팀은 문제가 모델에 있다고 생각합니다. 즉, '모델이 충분히 똑똑하지 않아서 잘못 보고하는 것'이라고 말입니다. 하지만 실제로는 정확도가 80%라고 해도, 20만 라인 규모의 코드베이스는 첫 스캔에서 수백 개의 유효한 발견 목록을 생성할 수 있습니다. 5명으로 구성된 팀이 이를 한 스프린트 만에 처리하기란 불가능합니다.
따라서 올바른 질문은 이것입니다: 이러한 발견 목록들 중에서, 무엇은 병합(merge)을 막아야 하고, 무엇은 백로그로 넘겨야 하며, 무엇은 무시해도 되는가? 이것이 바로 트리아지 문제입니다. 보안 분야에서는 이미 SAST 도구(Semgrep, CodeQL)를 통해 이 문제를 오랫동안 해결해 왔습니다. 우리는 그것을 AI 리뷰어에 적용하기만 하면 됩니다.
제가 사용한 세 가지 원칙은 다음과 같습니다:
- 새로운 발견 목록만 차단: 즉, PR이 변경된 라인에 있는 발견 목록만을 대상으로 합니다. 기존의 기술 부채(technical debt)는 베이스라인으로 처리합니다.
- 안정적인 지문(fingerprint) 확보: 동일한 이슈가 매번 실행할 때마다 다시 보고되지 않도록 해야 합니다.
- 예산(budget) 설정: PR당 최대 N개의 코멘트만 허용하고, 심각도(severity) 순으로 정렬합니다.
flowchart LR
A[AI Reviewer output] --> B[Normalize sang SARIF/JSON]
B --> C[Lọc theo diff của PR]
...
1단계: 변경된 라인에 있는 발견 목록만 유지하기
이 필터가 가장 효과적입니다. 많은 AI 리뷰어는 문맥을 파악하기 위해 전체 파일을 읽은 다음, 실제로는 PR과 전혀 관련 없는 곳의 문제까지 보고합니다. 개발자들은 불편함을 느끼고, 그들의 말이 맞습니다: '저는 3줄만 수정했는데, 왜 파일 전체를 리팩토링하라고 요구하는 건가요?'
아래 bash 스크립트는 PR에서 추가되거나 수정된 라인 목록을 가져오며, --unified=0 옵션의 git diff를 사용합니다:
#!/usr/bin/env bash
# changed_lines.sh - 모든 추가/수정된 라인을 file:line 형식으로 출력
set -euo pipefail
...
GitHub Actions에서 실행할 때는 actions/checkout@v4에 fetch-depth: 0을 설정해야 합니다. 그렇지 않으면 git diff가 base 커밋을 찾지 못합니다.
단계 2: 지문(Fingerprint) 및 기준선(Baseline)
AI 리뷰어는 비결정적(non-deterministic)입니다. 동일한 코드 조각에 대해 이번에는 'possible null dereference'라고 작성하고, 다음번에는 'variable may be None'이라고 작성할 수 있습니다. 따라서 메시지를 키로 사용하면 기준선은 아무런 효과가 없습니다.
저는 파일 + 카테고리 + 표준화된 코드 라인 내용을 기반으로 지문을 계산하며, 라인 번호(라인 번호는 상단에 코드가 삽입되면 변경되므로)나 메시지는 사용하지 않습니다. Python 3.11 이상 버전이며 외부 라이브러리가 필요 없습니다:
#!/usr/bin/env python3
# triage.py - 예산에 맞춰 finding을 필터링, 중복 제거 및 자름
import hashlib, json, re, sys
...
최초 기준선을 생성하려면 전체 main 브랜치에서 리뷰어를 실행하고, 모든 finding의 지문을 계산한 후 .ai-review-baseline.json에 저장하고 이 파일을 리포지토리에 커밋합니다. 이렇게 하면 이후부터는 오래된 부채가 새로운 PR을 방해하지 않습니다. 매 스프린트마다 기준선 처리를 위해 몇 시간을 할애하는 것이 기술 부채를 갚아나가는 방식과 같습니다.
단계 3: CI에 통합 및 피드백 루프 폐쇄
sequenceDiagram
participant Dev
participant CI as GitHub Actions
...
가장 마지막 단계이자 가장 중요하지만 종종 간과되는 것이 바로 **피드백 루프(feedback loop)**입니다. 개발자가 특정 finding을 오탐지(false positive)로 표시할 경우(예: PR에 /ai-ignore 주석 추가), 해당 지문은 기준선에 추가됩니다. gh CLI (버전 2.40 이상)를 사용하면 댓글을 읽는 것이 상당히 간단합니다:
gh api
또한 각 `category`별 false positive 비율을 추적하세요. 만약 `style` 카테고리가 70% 무시된다면, 해당 기능을 비활성화하고 대신 인간이 가장 놓치기 쉬운 버그 유형인 `security`와 `concurrency`에 댓글 예산을 할당하는 것이 좋습니다.
## 결론
Postgres 19 사례는 AI 리뷰어가 _너무 많은_ 발견 사항을 생성할 만큼 충분히 뛰어나다는 것을 보여주며, 이제 병목 지점은 인간의 처리 능력에 있습니다. 이번 주 바로 시도해 볼 수 있는 몇 가지 방법:
- **먼저 diff로 필터링**: PR이 변경된 라인에만 댓글을 달도록 하세요. 이 단계만으로 보통 50~70%의 노이즈를 제거할 수 있습니다.
- `main` 브랜치에 대한 **기준선(baseline)을 생성**하고 레포지토리에 커밋하여, 오래된 기술 부채가 새로운 PR을 막는 것을 방지하세요.
- 라인 수가 아닌 **코드 내용 기반으로 지문 인식(Fingerprint)**을 하고, 모델의 메시지를 사용하지 마세요.
- PR당 약 10개의 댓글 예산을 설정하고, 심각도(severity) 순서로 정리하며, `high`/`critical` 등급에 대해서만 CI를 실패시키도록 하세요.
- **피드백 루프 폐쇄**: false positive는 기준선으로 되돌아가야 하며, 카테고리별 무시 비율을 측정하여 미세 조정해야 합니다.
AI 리뷰는 팀이 실제로 읽을 때만 가치가 있습니다. 50개의 발견 사항 중 '해결됨(Resolve)' 버튼만 누른 것보다, 정확하고 처리된 5개의 발견 사항이 훨씬 유용합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기