버그를 수정하기 전에 증명하라
요약
AI 에이전트가 생성한 코드 수정 사항과 녹색 테스트 결과만으로는 버그가 실제로 존재했는지 확신하기 어렵다는 문제점을 지적합니다. 따라서 개발자는 단순히 코드를 검토하는 것을 넘어, 에이전트에게 '수정 전'의 동작을 재현하도록 요청하여 근본적인 문제를 확인해야 함을 강조합니다.
핵심 포인트
- AI 수정 사항만으로는 버그 존재 여부 확신 불가
- 녹색 테스트는 여러 의미를 가질 수 있음
- 에이전트에게 '재현(reproduce)' 질문 필수
- 수정 전의 실패 케이스 분석 중요성 부각
저의 go-tool-base 아키텍처 검토 결과, 21개의 치명적(critical) 및 높음(high) 등급의 발견 사항이 나왔는데, 이는 제가 혼자 처리하기에는 다소 많은 양이었습니다. 그래서 그들은 에이전트 무리에게 나갔고, 각각 하나의 발견 사항을 맡겼습니다. 저는 커피를 마시며 앉아 머지 리퀘스트(merge requests)가 도착하는 것을 지켜보았습니다.
그것들은 빠르게 도착했고, 21개 모두 녹색(green)이었습니다.
그리고 네 번째나 다섯 번째쯤에서, 제가 이것들 중 어떤 것이 진짜인지 전혀 알 수 없다는 것을 깨달았습니다.
작업 자체는 나쁘지 않았습니다. 오히려 그 반대였습니다: 합리적인 diff, 테스트 포함, 무엇이 잘못되었고 어떻게 올바르게 되었는지 설명하는 깔끔한 설명서까지. 사실상 완벽했습니다. 그리고 그것이 문제였습니다. 저는 문제가 해결되었다는 자신감 넘치는 보고들을 읽고 있었지만, 그 문제가 실제로 존재했었는지 확인할 방법이 없었습니다.
수정 사항과 함께 도착하는 녹색 테스트 스위트(green test suite)는 네 가지 다른 의미를 가질 수 있으며, 앉아 있는 저의 위치에서는 그것들을 구분할 수가 없었습니다. 수정 사항이 작동한 경우일 수도 있습니다. 아니면 버그가 애초에 존재하지 않았고 제가 아무것도 하지 않는 변경 사항을 그냥 받아들인 경우일 수도 있습니다. 혹은 수정 사항이 근처에 있던 다른 무언가를 해결했을 뿐인데 우연히 그것이 고장 난 것일 수도 있습니다. 또는, 가장 신경 쓰이는 것은, 테스트 자체가 변경된 코드와 함께 작성되었기 때문에 애초에 실패할 수 없었던 경우입니다.
diff를 읽어서는 이들을 구분할 수 없고, 에이전트가 diff에 대해 설명하는 글(최선의 경우 요약의 요약)을 읽어서도 결코 구분할 수 없습니다.
그래서 저는 흐름을 멈추고 모든 에이전트에게 수정하기 전에 자신의 버그를 재현했는지 물었습니다. 저는 지시가 아니라 질문으로 물었는데, 솔직히 저 자신도 몰랐기 때문이며, 그 답변이 21개의 수정 사항보다 더 중요했습니다.
돌아온 것들
go/errorhandling 모듈을 살펴보겠습니다. 이 모듈은 명령줄 도구(CLI)가 문제가 발생했을 때 어떻게 동작할지 결정합니다. 수정해야 할 버그는 다음과 같았습니다. 만약 CLI를 잘못 호출했다면, 예를 들어 서브커맨드를 잊었다면, 사용법 텍스트를 적절하게 출력한 후 0으로 종료했습니다. 성공! 스스로 매우 만족하는 상태였습니다. 이로 인해 if mytool; then과 같은 쉘 스크립트는 도구가 정상적으로 작동했다고 오해하고 순조롭게 진행되었습니다.
수정 사항은 대신 2로 종료하는 것입니다. 이는 "이 명령을 잘못 사용했다"는 일반적인 유닉스 코드이며, "실행했지만 실패했다"를 의미하는 1과는 구별됩니다.
따라서 테스트는 치명적인 오류(fatal error)에 대해 두 가지를 단언합니다. 첫째, 도구의 종료 함수(exit function)가 호출되는지 여부입니다. 둘째, 그 함수가 2로 호출되는지 여부입니다. 여기 수정 전, 변경된 라인 이전에 실행한 원본 테스트가 있습니다:
RED evidence (unmodified main logic, pre-fix):
- fatal_ErrRunSubCommand_exits_2_and_prints_usage FAILED:
"exit-called expectation" expected true actual false;
...
이것은 읽기에는 노이즈처럼 보이지만 라인별로 분석해야 합니다.
expected true actual false는 첫 번째 실패한 단언입니다. 종료 함수가 호출된 적이 없었다는 의미이며, 잘못된 숫자로 호출되거나 너무 늦게 호출된 것이 아니라 아예 호출되지 않았다는 뜻입니다. 그리고 expected 2 actual -1은 다른 관점에서 같은 사실을 보여줍니다. 왜냐하면 -1은 테스트의 대체 종료 함수(stand-in exit function)가 아무것도 호출되지 않았을 때 보고하는 값이기 때문입니다. 실제로는 존재하지 않는 종료 코드인 것입니다.
그리고 네 번째 라인을 보세요. 비치명적인 경우를 다루는 다른 두 개의 서브테스트는 정반대의 것을 단언합니다. 즉, 경고(warning)가 프로세스를 종료해서는 안 된다는 것입니다. 이 테스트들은 수정 전에도 통과했으며, 그 라인이 제가 전체 작업에 대해 마음을 바꾸게 만든 부분입니다.
그것은 네거티브 컨트롤(negative control)이며, 빨간 결과(red result)를 증거로 바꾸는 것입니다. 즉, 두 가지가 고장 났고, 두 가지 밀접하게 관련된 것은 명백히 정상이라는 것을 보여주며, 그 사이에 명확한 경계선이 있어야 합니다. 이것 없이는 'FAILED'라는 벽은 단지 어딘가에 문제가 생겼다는 것만 알려줄 뿐입니다. 이는 증거라기보다는 스크린샷일 뿐이며, 테스트 하네스(test harness) 자체가 고장 나서 모든 것을 실패로 만들고 있을 가능성도 배제할 수 없습니다.
go/credentials 역시 동일한 형태의 결과를 반환했습니다. 이 모듈의 Probe 호출은 자격 증명 저장소(credential store)가 실제로 사용 가능한지 확인하는 것이 목적이며, 시간이 지나면 응답하지 않을 경우 포기하도록 설계되어 있습니다. 에이전트는 모든 작업이 영원히 블록되는 의도적으로 적대적인 저장소를 작성했고 (실제 배포할 만한 저장소는 아니지만, 그것이 요점입니다), 이 Probe를 100밀리초의 마감 시간과 함께 호출했으며, 테스트에는 2초짜리 가드(guard)를 설정했습니다. 그 가드가 작동했습니다: Probe가 여전히 기다리고 있었고, 지켜야 할 마감 시간을 훨씬 초과했기 때문입니다. 그리고 마감 시간이 강제된 후, 동일한 테스트는 가드 시간보다 훨씬 짧은 시간에 통과했습니다.
이 두 가지 중 어느 것도 영리하지 않다고 생각하며, 저는 바로 그 점이 좋습니다. 요청하기 쉽고, 한눈에 읽기도 쉬우며, 우연히 만들어내기 매우 어렵습니다. 성실함을 '들리는' 요약문을 작성하는 데는 약 4초밖에 걸리지 않습니다. 손대지 않은 main을 대상으로 실패한 실행 결과를 만든 다음, 동일한 실행 결과가 녹색으로 나오고, 형제 테스트(sibling tests)들이 전반적으로 합리적으로 작동한다는 것은 실제로 그 작업을 수행했다는 의미입니다.
제가 계속해서 요구하는 이유
저는 에이전트가 작성하는 모든 줄을 검토합니다. 이렇게 들린다는 것을 알고 있지만, 사실이며, 또한 가장 먼저 무너지는 부분이기도 합니다. 전체 시스템에서 21개의 발견 사항은 이미 모든 diff를 제대로 읽는 것이 성과라기보다는 실제 작업에 해당할 지점을 넘어섰으며, 이 시스템은 피로해지거나 창피함을 느끼지 않고 일주일 내내 저보다 더 많은 결과물을 생산할 것입니다 (실제로 그렇게 했습니다, 여러 번).
검증(verifying)이 가장 비용이 많이 드는 부분이며, 작업 속도가 빨라지면 가장 먼저 줄어드는 부분이기도 합니다. 따라서 리뷰 방식은 강도보다는 형태를 바꿔야 합니다. 즉, '변경 사항을 읽으려 하기'보다 '변경 사항이 가져온 증거들을 확인하는 것'에 더 집중해야 한다는 것입니다. 제가 찾은 가장 저렴한 종류의 증거는 Red-first이며, 한 번의 시각적 검사만으로도 에이전트가 문제를 해결하기 전에 그 문제를 입증했는지 여부를 알 수 있습니다.
결국 21개 모두 이 관문을 통과했습니다. 각각은 빨간색(red), 초록색(green), 그리고 그 사이에 의미를 유지하는 형제 테스트들(sibling tests)을 가지고 도착했고, 저는 그렇지 않았다면 읽었을 코드 양보다 훨씬 적게 읽으면서도, 훨씬 더 큰 확신을 가질 수 있었습니다.
2026년 10월 3일 phpboyscout.uk에 최초 게시됨.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기