스캐너가 찾아낼 수 없는 로직 버그
요약
정적 분석 스캐너가 잡아낼 수 없는 로직 버그의 한계와 LLM을 활용한 코드 리뷰의 필요성을 다룹니다. 스캐너는 구문론적 패턴을 찾는 데 유용하지만, 코드의 의도와 런타임 동작을 파악하는 의미론적 정확성은 LLM의 추론 능력이 필요함을 설명합니다.
핵심 포인트
- 스캐너는 구문론적 패턴은 찾지만 의미론적 정확성은 증명할 수 없음
- 라이스의 정리에 따라 프로그램의 동작을 결정하는 것은 이론적으로 불가능함
- 로직 버그는 패턴 매칭이 아닌 코드의 의도에 대한 추론으로 해결해야 함
- BrassCoders와 같은 도구는 패턴을, LLM은 정확성을 담당하는 역할 분담이 중요함
스캐너는 코드가 알려진 '나쁜 형태 (shape)'와 일치하는지를 증명할 뿐, 코드가 '올바른 일 (thing)'을 수행하는지는 증명할 수 없습니다. 0이 될 수 있는 길로 평균을 구하는 계산이나, 마지막 레코드를 조용히 누락시키는 오프 바이 원 (off-by-one) 오류와 같은 로직 버그 (logic bug)는 구조적인 마커가 없어 어떤 규칙과도 일치하지 않는, 잘못되었지만 형식은 잘 갖춰진 계산입니다. 이것이 결정론적 스캐너 (deterministic scanner)가 이를 잡아내지 못하는 이유이며, BrassCoders가 패턴으로 이를 표현할 수 있는 척하는 대신 LLM 레이어에 정확성 (correctness)을 맡기는 이유입니다.
이것은 두 종류의 코드 리뷰 (code review) 사이의 경계이며, 어느 한쪽을 신뢰하기 전에 그 경계가 정확히 어디인지 아는 것이 중요합니다.
스캐너가 증명할 수 있는 것과 없는 것
BrassCoders는 구문론적 (syntactic) 속성 — 즉, 이 코드가 위험하다고 알려진 패턴을 포함하고 있다는 점 — 을 증명하며, 매 실행 시 동일한 방식으로 이를 증명합니다. 하지만 코드가 의도한 대로 계산되는지의 문제인 의미론적 (semantic) 속성은 증명할 수 없습니다. 왜냐하면 그 질문에는 일치시킬 고정된 형태가 없기 때문입니다.
이 구분은 명확합니다. hashlib.md5(password)는 나쁜 형태입니다. 비밀번호 해싱을 위한 MD5는 어디에서 나타나든 잘못된 것이므로 규칙이 이를 플래그 (flag) 합니다. 반면 sum(readings) / len(readings)는 형태 자체는 괜찮지만 readings가 비어 있을 때만 잘못됩니다. 그리고 소스 코드 상에서 빈 리스트는 데이터가 가득 찬 리스트와 동일하게 보입니다. 첫 번째는 패턴입니다. 두 번째는 런타임 동작 (runtime behavior)에 관한 사실입니다. 스캐너는 패턴을 봅니다.
여기에는 이론적인 한계가 존재합니다. 라이스의 정리 (Rice's theorem)에 따르면, 프로그램이 어떻게 작성되었는가가 아니라 프로그램이 '무엇을 하는가'에 대한 모든 비자명한 (non-trivial) 질문은 일반적으로 결정 불가능 (undecidable) 합니다. 정확성 (correctness)은 코드가 무엇을 하는지에 대한 질문입니다. 아무리 패턴 엔지니어링 (pattern engineering)을 해도 그 한계를 넘을 수 없습니다. 그것은 도구의 결함이 아니라 계산 (computation) 자체의 속성입니다.
BrassCoders가 놓친 버그
BrassCoders는 공개된 벤치마크에서 단 하나의 발견을 놓쳤습니다: 빈 리스트에 대한 가드(guard)가 없는 sum(readings) / len(readings)입니다. 이는 불행한 경로(unhappy path)를 기다리는 ZeroDivisionError였습니다. 같은 코드를 검토한 LLM이 이를 포착했습니다. 그 결과는 한 줄로 그려진 경계선입니다.
이 누락은 튜닝 실패가 아닙니다. '리스트가 비어 있을 때 이 나눗셈은 안전하지 않다'라는 규칙을 표현하더라도, 코드베이스의 모든 올바른 평균값까지 플래그 지정(flagging)하게 되는데, 이는 안전한 버전과 안전하지 않은 버전이 구조적으로 동일하기 때문입니다. 그 모든 것을 플래그 지정하는 스캐너는 노이즈가 될 것이고, 아무것도 플래그 지정하지 않는 스캐너는 BrassCoders가 패턴이 무엇을 볼 수 있는지에 대한 진실을 보고하는 것입니다. 이 버그는 'readings가 비어 있을 때 무슨 일이 발생하는가?'라는 질문을 던지는 리더(reader)를 필요로 했습니다. 이는 의도(intent)에 관한 질문이며, 매칭(matching)이 아닌 추론(reasoning)으로 답변됩니다.
이것이 바로 brass 대 Claude의 역할 분담을 가장 깨끗하게 보여주는 사례입니다. BrassCoders는 결정론적 패턴 보고기(deterministic pattern reporter)이며, 발견 사항을 읽는 AI 어시스턴트는 의도 인식 검토자(intent-aware reviewer)입니다. 이 벤치마크는 각자가 자신의 역할을 수행하고 있으며 어느 쪽도 다른 쪽을 위장하지 않는다는 것을 보여줍니다.
시장이 LLM 검토자를 찾는 이유
AI 생성 코드에서 로직 버그를 포착한다고 주장하는 도구들의 물결은 근본적으로 LLM 검토자들입니다. 그리고 이것은 우연이 아닙니다. 논리적 정확성(Logic correctness)은 코드가 무엇을 해야 하는지에 대한 추론을 요구하며, 의도에 대한 추론이야말로 패턴 매처(pattern matchers)가 할 수 없는 언어 모델의 능력입니다. 시장은 결정론적 계층이 구조적으로 정확성에 미치지 못하기 때문에 LLM 레이어를 찾게 된 것입니다.
이는 2계층 아키텍처 (two-layer architecture)를 약화시키는 것이 아니라 오히려 정당화합니다. 구조적 사전 검사 (structural pre-pass) 없이 코드를 처음부터 읽는 모델은 로직에 도달하기도 전에 MD5 호출이나 연결된 SQL과 같은 지루한 결과들을 재도출하는 데 주의력 (attention)을 소모합니다. 결정론적 스캔 (deterministic scan)을 먼저 실행하면 이러한 항목들은 이미 플래그가 지정되어 있으므로, 모델의 추론 예산 (reasoning budget)은 빈 리스트 가드 (empty-list guard)나 오프 바이 원 (off-by-one) 오류에 집중될 수 있습니다. 스캐너가 구조적 계층을 정리하면, 모델은 그 위의 계층을 작업합니다.
결정론적 계층이 멈추는 지점
BrassCoders는 형태가 있는 버그는 잡아내고, 형태가 없는 버그는 추측하기를 거부합니다. 이 원칙이 설계의 전부입니다. SQL 인젝션 (SQL injection), 취약한 암호 (weak cipher), 하드코딩된 키 (hardcoded key), shell=True 호출은 모두 구조적 마커 (structural markers)를 남기므로 결정론적이고 재현 가능하게 플래그가 지정됩니다. 반면 로직 오류 (logic error)는 아무런 마커도 남기지 않으므로, 이를 추론할 수 있는 리뷰어에게 전달됩니다.
경계선을 명확히 정의하는 것이 도구를 신뢰할 수 있게 만듭니다. 정확성을 검증한다고 주장하는 스캐너는 확인할 방법이 없는 의도 (intent)를 추론하게 될 것이며, 코드의 정확성 여부에 대해 확신을 가지고 틀린 답을 내놓는 것은
pip install brasscoders
brasscoders --offline scan /path/to/your/project
스캔은 형태(shape)가 있는 모든 버그를 잡아냅니다. 형태가 없는 로직 버그(logic bug)를 찾아내는 것은 리뷰어의 몫이며, 신뢰할 만한 도구라면 무엇이 무엇인지 알려줄 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기