단언하지 말고 대면하라
요약
AI 코드 감사 도구가 문서와 실제 코드 사이의 불일치를 찾아내는 방식에 대해 다룹니다. 정밀도(Precision)와 재현율(Recall) 사이의 트레이드오프를 분석하며, 단순히 노이즈를 줄이는 것을 넘어 실제 버그를 포착하는 능력이 중요함을 강조합니다.
핵심 포인트
- 문서와 실제 코드 간의 불일치는 심각한 잠재적 버그를 유발함
- AI 코드 도구는 정밀도 우선과 재현율 우선 진영으로 나뉨
- 단순히 노이즈를 줄이는 것보다 실제 오류를 찾아내는 능력이 핵심임
- Greptile과 CodeRabbit의 벤치마크를 통해 도구별 성능 차이 제시
왜 당신의 AI 코드 감사 도구는 무엇이 잘못되었는지 설명할 수 있어야 하는가
Docstring(문서화 주석)은 해당 쓰기 작업이 안전하다고 말했습니다. 아키텍처 문서도 마찬가지였습니다. 둘 다 리뷰를 거쳤고, 둘 다 신뢰받았습니다. 그 문서들은 liveness update(활성 상태 업데이트) — 레코드를 "진행 중"으로 전환하는 종류의 작업 — 를 설명하고 있었으며, 가드(guard) 조건에 대해 구체적이었습니다. 문서에 따르면, 쓰기 작업은 레코드의 _status(상태)_에 따라 조건부로 이루어지므로, 완료된 레코드가 뒤늦게 경합하는(racing) 작성자에 의해 다시 열리는 일은 결코 없을 것이라고 했습니다. 필터 내의 Compare-and-swap(CAS). 교과서적인 방식이었습니다.
하지만 코드는 id로만 레코드를 비교했습니다. 오직 id로만 말이죠. 문서가 약속했던 status(상태) 조건문은 그곳에 없었습니다.
당장 불이 난 상황은 아니었습니다. 실제로 작성자는 한 명뿐이었기에, 누락된 가드로 인해 발생할 수 있었던 race(경합) 상황은 한 번도 유발된 적이 없었습니다. 하지만 문서가 설명했던 가드 — 미래의 유지보수자가 읽고, 신뢰하고, "이미 처리되어 있다"며 추가하기를 거부하게 될 그 가드 — 는 존재하지 않았습니다. 문서는 오래된 것이 아니었습니다. 그것은 애초에 사실인 적이 없었습니다. 그리고 문서는 충분히 확신에 차 있었기에, 그 어떤 리뷰어도 문장이 설명한다고 주장하는 단 한 줄의 코드와 대조하여 확인해 볼 생각을 하지 못했습니다.
저는 주의 깊게 읽어서 이것을 찾아낸 것이 아닙니다. 확신에 찬 주장이 현실과 일치할 때까지 유죄로 간주하고, 동시에 그 주장이 어떻게 틀릴 수 있는지 나에게 알려주는 것을 전담하는 도구를 만들었기 때문에 찾아낼 수 있었습니다.
그 두 번째 절이 이 에세이의 핵심입니다.
우리가 거꾸로 만든 것
이것이 2026년 AI 코드 툴링(tooling)의 모습입니다. 이 분야는 이미 중요한 축을 찾아냈지만, 단지 그 축의 잘못된 끝을 기본값으로 신성시했을 뿐입니다.
한쪽에는 침묵을 유지하도록 조정된, 정밀도 우선 (precision-first) 검토자들이 있습니다. 이들은 덜 잡아내더라도, 거의 결코 양치기 소년이 되지 않으려 합니다. 다른 한쪽에는 더 많이 잡아내기 위해 노이즈 (noise)를 감수하는 재현율 우선 (recall-first) 진영이 있습니다. 이 분열은 실재하며 측정 가능합니다. 동일한 버그에 대해 이들을 실행해 보면 그 격차는 엄청납니다. Greptile의 자체 50-PR 벤치마크 — 수정 커밋으로부터 재구성된 실제 프로덕션 버그들 — 에서 Greptile은 약 82%를 잡아내는 반면 CodeRabbit은 44%를 잡아냅니다. 즉, 재현율 (recall)에 맞춰 조정된 도구와, 포착률을 희생하여 더 조용한 신호를 제공하도록 정밀도 (precision)에 맞춰 조정된 도구의 차이입니다. 두 가지 철학 모두 제품으로 출시되어 있으며, 둘 다 방어 가능합니다.
조용한 쪽도 타당한 논거를 가지고 있으며, 저는 그 가장 강력한 형태에 대해 인정할 것입니다. 만약 당신이 풀 리퀘스트 (pull-request) 시점에 개발자의 흐름 (flow)을 방해하고 있다면, 조용한 것이 '옳습니다'. 노이즈에는 비용이 따르며, 그 비용은 인간이 지불합니다. OpenAI는 CriticGPT를 구축하면서 이를 확인했습니다. 트레이너들은 베이스라인 (baseline)에 비해 63%의 확률로 CriticGPT의 비평을 선호했는데, 이는 부분적으로 이 모델이 도움이 되지 않는 사소한 지적 (nitpicks)을 덜 생성했기 때문입니다. 거짓 양성 (false positives)을 줄이는 것은 어리석은 목표가 아닙니다. 적절한 상황에서는 그것이 유일한 목표이기도 합니다.
따라서 저는 노이즈를 줄이려는 본능이 어리석다고 말하려는 것이 아닙니다. 여전히 두 가지가 잘못되었으며, 이 두 가지는 잘못 조정된 임계값 (threshold)보다 더 심각한 문제입니다.
첫 번째는 변수 (variable)입니다. 신뢰도 (Confidence)는 임계값을 설정하기에 잘못된 지표입니다. 어느 쪽으로 설정하든 말이죠. 모델의 신뢰도는 정확성 (correctness)에 대한 측정이 아닙니다. 그것은 모델이 학습한 텍스트의 속성일 뿐이며, 그 텍스트는 압도적으로 확신에 찬 것처럼 들리는 사람들에 의해 작성되었습니다. UT Dallas에서 진행한 2026년 TRACE라는 연구는 제가 서두에서 언급한 작업 — 코드가 문서를 준수하는가? — 에 대해 7개의 모델을 대상으로 이를 측정했으며, 7개 중 6개 모델의 신뢰도가 제대로 보정 (calibrated)되지 않았음을 발견했습니다. 당신이 필터링 기준으로 삼는 그 숫자는 발견 내용이 참인지 여부를 추적하지 않습니다. 따라서 신뢰도로 필터링하는 것은 진실을 필터링하는 것이 아닙니다. 그것은 유창성 (fluency)을 필터링하는 것입니다. 이는 거짓을 말하는 문서를 만들어낸 것과 동일한 실패 모드 (failure mode)입니다.
그리고 그 필터가 무엇을 통과시키는지 보십시오. LLM 심사위원 (LLM judges)의 측정된 약점은 양 (volume)이 아니라 재현율 (recall)입니다. GPT-3.5 시대의 요약 일관성 테스트 (summarization-consistency tests)에서, 심사위원들은 일관되지 않은 요약의 30~60%만을 잡아냈습니다. 즉, 이들은 가짜 결함을 만들어내는 것보다 실제 결함을 훨씬 더 쉽게 놓칩니다. AI 감사자 (AI auditor)의 지배적인 결함은 거짓 양성 (false positive)이 아니라 거짓 음성 (false negative)입니다. "표면적으로 높은 신뢰도가 있는 결과만 출력하라"는 지침은 시끄러운 거짓 양성은 쳐내지만, 조용한 거짓 음성은 정확히 원래 있던 자리에 그대로 남겨둡니다. 이는 이미 괜찮았던 단 하나의 수치를 최적화할 뿐입니다.
상황은 더 심각해집니다. 동일한 연구는 이러한 감사자들이 어디에서 눈이 머는지 수치로 나타냈으며, 그 결과는 불편할 정도로 정확하게 제 이야기와 일치합니다. 문서가 명백하게 틀렸을 때 모델은 67~94%의 확률로 이를 잘 잡아냅니다. 거짓 주장은 크고 국소적인 모순이기 때문입니다. 실패 사례는 그 반대입니다. 코드가 조용히 문서를 준수하지 못하는 동안, 문서 자체는 그럴듯하게 (plausible) 유지되는 경우입니다. 최악의 경우 이러한 사례의 탐지율은 40포인트 이상 떨어집니다. 그리고 신뢰도 (confidence)가 잘못 보정되어 있기 때문에, 모델은 자신이 사각지대에 서 있다는 사실조차 당신에게 말할 수 없습니다. 그것이 바로 거짓말을 한 문서입니다. 그것은 나쁜 가드 (guard)를 추천한 것이 아닙니다. 그것은 좋은 가드를 완료된 것으로 묘사했습니다. 겉보기에는 그럴듯하지만 코드와 대조했을 때만 거짓인 것입니다. 실질적인 측면에서는 옳지만 현실에 대해서만 틀린 주장은 감사자나 인간 검토자 모두에게 가장 발견하기 어려운 것입니다. 그렇기에 그것을 읽은 모든 사람이 고개를 끄덕이는 동안, 그 문서는 아무런 수정 없이 그대로 방치되었습니다.
두 번째는 환경(the room)입니다. 시끄럽든 조용하든 이 모든 도구들은 풀 리퀘스트 (Pull-request) 시점에 작동합니다. 즉, 소음이 실제로 비용을 발생시키는 개발자의 흐름 (flow) 속으로 파고듭니다. 하지만 1년 동안 가만히 놓여 있던 코드에 동일한 태도를 적용하면 경제 논리는 뒤집힙니다. 방해할 흐름도 없고, 생각에 잠긴 리뷰어도 없으며, 소음으로부터 보호해야 할 것도 없습니다. 조용히 해야 할 이유가 증발해 버리는 것입니다. 그러면 남는 것은, 침묵의 비용이 가장 크게 발생하는 곳, 즉 서 있고 신뢰받으며 검토되지 않은 코드에 대해 여전히 입을 다물도록 훈련된 도구뿐입니다.
거짓을 말한 문서는 그 침묵이 내부에서 어떻게 보이는지를 보여줍니다. 기이한 버그가 아닙니다. 아주 평범한 버그입니다. 즉, 참처럼 들리고 모두가 신뢰하며, 그것을 반증하기 위해 만들어진 것이 아무것도 없는 주장 말입니다.
단언하지 말고 대면하라
여기 이 도구가 구축된 원칙이자, 저소음 (low-noise)에 대한 전체적인 합의에 맞서 제가 옹호하고자 하는 원칙이 있습니다.
어떤 주장은 그것을 반증할 수 있는 무언가가 있기 전까지는 아무런 가치가 없습니다. 코드의 주장이 아니라, _감사자 (auditor)_의 주장 말입니다. 만약 제 도구가 쓰기 작업이 보호되지 않았다고 알려준다면, 동일한 결과 내에서 그것이 틀릴 수 있는 조건도 함께 알려주어야 합니다: "만약 상위 호출자 (upstream caller)가 이미 이 경로를 진행 중인 레코드 (in-flight records)로 제한하고 있다면, 이 필터 술어 (filter predicate)는 불필요하므로 이 결과는 부정확합니다." 이 문장은 회피성 발언이 아닙니다. 이 문장은 보고서에서 가장 유용한 줄입니다. 왜냐하면 해당 결과(finding)를 반박하기 위해 정확히 어디를 살펴봐야 하는지 알려주기 때문입니다. 그리고 만약 반박할 수 없다면, 당신은 실질적인 무언가를 배우게 된 것입니다. 이는 물리학적 습관을 이식한 것입니다: 계산이 결과를 만들어냈기 때문에 그 결과를 신뢰하는 것이 아니라 — 그것을 깨뜨리려 시도했음에도 실패했기 때문에 신뢰하는 것입니다.
그로부터 세 가지 약속이 도출되며, 이는 현재 업계의 흐름과는 정반대되는 것입니다:
모든 발견 사항(finding)은 그것이 어떻게 틀릴 수 있는지를 명시합니다. 발견 사항마다 반증 가능성(falsifiability) 조항을 두는 것입니다. 이것은 단순한 UI상의 친절함처럼 들릴 수 있습니다. 하지만 실제로는 완전히 다른 인식론(epistemics)입니다. 이는 감사자(auditor)가 단순히 신뢰도 점수가 붙은 '느낌(vibe)'을 내뱉는 대신, 공격받을 수 있을 만큼 충분히 구체적인 세계 모델을 보유하도록 강제합니다.
침묵은 커버리지(coverage)가 아닙니다. 만약 감사자가 아무것도 발견하지 못했다면, 그것은 "아무것도 없다"는 것과 동일하지 않습니다. 그것은 단지 살펴보지 않았음을 의미할 수도 있습니다. 따라서 보고서에는 _확인되었고 깨끗하다고 판명된 것_을 위한 일급 시민(first-class) 섹션을 둡니다. 이는 검증되지 않은 기록(unguarded write)의 조심스러운 형제와 같으며, 그것이 왜 괜찮은지에 대한 이유("술어(predicate)가 필터에 포함되어 있음; 이것은 올바름")와 함께 명명됩니다. 빈 발견 사항 목록 또한 하나의 주장이며, 마땅히 그 증거를 수반해야 합니다. 인간이 수십 년간 사용해 온 감사 표준은 이미 이런 방식으로 작동합니다: 범위(scope), 무엇을 조사했는지, 무엇을 발견했는지 말입니다. AI 리뷰는 이를 조용히 버리고 오직 경보(alarms)만을 남겨두었습니다.
반박할 수 없는 발견 사항은 장식에 불과합니다. 발견 사항이 배포되기 전, 오직 그것을 _죽이는 것(kill)_만이 유일한 임무인 적대자(adversary)가 이를 상대로 실행됩니다: 경합 경로(racing path)가 실제로 공동 실행되는가? 누락된 가드(guard)가 한 단계 상위에 존재하는가? 인용된 경로가 실제로 도달 가능한가? 오직 공격에서 살아남은 발견 사항만이 보고됩니다. 이것은 법정을 보도 자료가 아닌 적대적(adversarial)인 공간으로 만드는 것과 동일한 움직임입니다. 그리고 "AI가 문제를 발견했다"는 콘텐츠 중 얼마나 많은 양이 이 과정을 견디지 못할지는 놀라울 정도입니다.
여기서 설명하는 메커니즘이 생소한 것은 아니며, 그렇지 않은 척하지도 않겠습니다. Greptile의 최신 버전들은 플래그(flagged)가 지정된 이슈가 사용자에게 도달하기 전에, 그것이 실제 문제인지 결정하기 위해 호출 체인(call chain)을 따라 추적하는 에이전트(agent)를 실행합니다. 이는 인간이 보기 전에 발견 사항을 제거하려는 동일한 본능입니다. 제가 구분 짓고자 하는 차이점은 적대적 요소(adversary)가 존재하느냐가 아닙니다. 적대적 요소의 판결이 보고서 안에 포함되어야 한다는 점입니다. 살아남은 발견 사항은 그것이 견뎌낸 반박(refutation) — 즉, 누군가가 충족하려 했으나 충족하지 못한 조건 — 과 함께 전달됩니다. 따라서 독자는 단순히 판결만이 아니라 공격 과정 자체를 물려받게 됩니다. 당신이 실행하고 버리는 검증은 도구의 정밀도(precision)를 보호하지만, 독자에게 전달하는 검증은 독자의 판단력을 보호합니다. 당신이 키보드에서 손을 떼고 떠난 뒤에도 살아남는 것은 오직 후자뿐입니다.
이 중 그 어떤 것도 감사자(auditor)를 더 똑똑하게 만드는 것에 관한 것이 아닙니다. 이것은 감사자를 정직하게 만드는 것에 관한 것이며, 당신이 확인할 수 있는 방식으로 정직하게 만드는 것에 관한 것입니다.
감사자 또한 반증 가능해야 한다
한 단계 더 높은 곳에 함정이 있으며, 이것이 바로 이러한 수사학의 대부분을 실무에서 가치 없게 만드는 요소입니다. 반증 가능성(falsifiability)을 중시하는 감사자라 할지라도 여전히 **공허(vacuous)**할 수 있습니다. 즉, 아무것도 찾아내지 못함으로써
이것은 아이디어 측면에서 새로운 것이 아닙니다. OpenAI가 CriticGPT를 검증한 방식도 사람들이 미묘한 버그를 삽입하도록 비용을 지불하고, 비평가(critic)가 이를 잡아내는지 측정하는 방식이었습니다. 이는 변이 테스트 (mutation testing)와 같은 본능입니다. 의도적으로 망가뜨린 프로그램에 의해 실패할 수 없는 테스트 스위트는 아무것도 보호하지 못합니다. 이상한 점은 우리가 출시하고 있는 AI 감사자 (AI auditors)들에게 이 방식이 거의 적용되지 않는다는 것입니다. 우리는 코드를 테스트합니다. 하지만 코드를 심판하는 대상을 테스트하지는 않습니다. 실패(red)할 수 없는 테스트는 불활성(inert) 상태입니다 — 그리고 그 결과가 사라지게 만들 수 없는 감사자는 진행 표시줄만 달린 장식품에 불과합니다.
불편한 진실을 솔직하게 말씀드리겠습니다. 오늘날 구매할 수 있는 대부분의 "AI가 당신의 코드를 검토했습니다"라는 서비스는, 심어놓은 결함을 확실히 잡아내고, 그 결함이 수정되었을 때 확실히 잡아내는 것을 멈추는지(즉, 결함이 사라졌음을 인지하는지)를 단 하나도 보여주지 못합니다. 그 시연을 요구하십시오. 그것은 정당한 질문이며, 이것이 게임의 전부입니다.
당신이 자리에 있을 때만 작동한다면
어떤 규율(discipline)이 실제인지 아니면 단순한 선호(preference)인지 확인하는 테스트 방법이 있습니다. 당신에게 아무것도 물어볼 수 없는 대상에게 그 규율을 맡겨보고, 그것이 유지되는지 확인하는 것입니다. 저는 원격 에이전트 (remote agents)를 위한 프롬프트를 작성합니다. 이들은 저의 기준에 대한 기억도 없고, 공유된 컨텍스트도 없으며, 질문을 다시 던질 방법도 없습니다. 이때 제가 다른 무엇보다 최우선으로 두는 규칙은 이 에세이에서 다루는 것과 동일한 칼날이며, 기계를 겨냥합니다: 버그 보고 텍스트를 읽는 것은 증거로서 0점이며, 검증된 재현(reproduction)만이 1점으로 간주된다. 0점에 근거하여 행동하지 마라. 프롬프트는 에이전트가 자신의 작업을 스스로 검증하도록 가르쳐야 합니다. 왜냐하면 아무도 대신 해주지 않을 것이기 때문입니다 — 저는 정확히 그러한 확인 절차가 부족하여, 권위 있어 보이는 데이터가 확신에 찬 잘못된 결론으로 직행하는 과정을 목격한 적이 있습니다.
낯선 이가 따를 수 있는 지침으로 압축할 수 있는 확신 — 즉, 행동하기 전에 주장을 대면하는 것 — 이 바로 확신이지, 취향이 아닙니다. 만약 당신이 자리에 있을 때만 작동한다면, 그것은 결코 규율이 아니었습니다.
왜 사후적(retrospective)이어야 하며, 왜 기둥(pillar)별이어야 하는가
설계상의 선택 사항이 하나 더 있습니다. 왜냐하면 거짓을 말하고 있던 문서가 실제로 존재했던 곳이기 때문입니다.
이 시장의 혼잡하고 자본이 풍부한 영역은 _diffs (차이점)_를 검토합니다. 즉, 풀 리퀘스트 (pull-request) 시점에 변경 사항을 차단(gate)합니다. 그것은 가치 있는 일이며 저는 그것과 경쟁하려는 것이 아닙니다. 하지만 거짓을 말하던 문서는 diff에 들어있지 않았습니다. 그것은 내내 검토되지 않은 채 저장소 (repository)에 그대로 놓여 있었습니다. 차단할 변경 사항 자체가 없었던 것입니다. 그 문서는 지구상의 모든 풀 리퀘스트 (pull-request) 리뷰어를 무사히 통과했을 것입니다. 왜냐하면 그들 중 누구도 움직이지 않는 코드는 보고 있지 않았기 때문입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기