Stop Asking AI to Fix the Bug. Ask It to Prove the Cause.
AI에게 버그 수정을 맡기기 전에 원인부터 입증하게 하세요
AI가 제안한 패치로 증상이 사라져도 원인을 해결했다고 단정할 수는 없습니다. 재현 절차와 로그로 가설을 검증하고, 원인에 맞는 최소 수정과 회귀 테스트를 확인하는 디버깅 흐름을 제안합니다.
- 주제
AI 요약
AI에게 오류를 보여주고 “고쳐 달라”고 하면 조건문이나 null 검사, try/catch를 추가해 증상을 없애는 답을 내놓곤 합니다. 하지만 오류가 사라졌다는 사실만으로 원인을 찾았거나 수정이 문제를 해결했다고 확인할 수는 없습니다. 이 글은 AI에게 코드를 바로 바꾸게 하기보다, 실패를 재현하고 원인을 뒷받침하는 증거를 찾도록 요청하라고 제안합니다.
증상과 원인을 구분합니다
검색창에 새 검색어를 입력했는데 이전 결과가 간헐적으로 나타나는 상황을 예로 듭니다. “검색이 고장 났다”는 설명만 주면 AI가 여러 원인을 추측할 여지가 큽니다. 대신 기대 동작은 현재 검색어에 맞는 결과 표시, 실제 동작은 이전 검색 결과가 나중 결과를 덮어쓰는 현상, 재현 조건은 검색어를 빠르게 바꾸는 상황이라고 구체적으로 적습니다. 느린 네트워크에서 더 쉽게 재현된다는 점과 관련 코드도 함께 제공합니다.
이후 코드 변경은 보류하고, 가능한 원인을 최대 세 가지로 제시하게 합니다. 관찰한 사실과 가정을 분리하고, 각 가설을 지지하거나 배제할 증거와 가장 작은 재현 방법을 설명하도록 요청합니다. 그럴듯한 수정안을 곧바로 받아들이는 대신 검증할 가설을 얻는 방식입니다.
재현으로 메커니즘을 확인합니다
검색 요청 A가 먼저 시작되고, 새 검색어에 대한 요청 B가 뒤이어 시작되는 상황을 살펴봅니다. B가 먼저 끝나 올바른 결과를 표시한 뒤 A가 늦게 끝나면, 오래된 응답이 최신 결과를 덮어쓸 수 있습니다. 디바운싱(debouncing)은 요청 시작 횟수를 줄이지만 응답 순서를 보장하지 않습니다. 따라서 문제를 덜 자주 보이게 해도 원인을 해결하지 못할 수 있습니다.
글은 검색어에 따라 인위적인 지연을 주는 fakeSearch 예시로 응답 순서가 뒤집히는 상황을 통제해 재현하라고 설명합니다. 다만 재현은 특정 메커니즘이 실패를 일으킬 수 있음을 보여줄 뿐, 실제 애플리케이션에서도 그 원인이 발생했다는 증명은 아닙니다. 재현이 무엇을 입증하고 무엇은 입증하지 못하는지 AI에게 설명하게 해야 합니다.
실제 로그로 가설을 검증합니다
무작정 로그를 늘리기보다, 가설을 구별하는 데 필요한 정보만 기록합니다. 예시에서는 요청 시작과 종료, 결과를 UI에 반영하는 시점마다 검색어와 요청 ID를 남깁니다. 새 요청인 id=42가 먼저 끝나 화면에 반영된 뒤 이전 요청 id=41의 결과가 다시 반영되는 기록은 오래된 응답이 UI를 덮어쓴다는 설명을 뒷받침합니다.
로그나 측정값마다 어떤 질문에 답하는지, 어떤 가설을 배제하는지 설명하게 합니다. 비밀이나 개인정보를 기록하지 말라는 주의도 덧붙입니다. 실제 로그가 가설과 맞지 않으면 재현을 억지로 맞추지 말고 원인을 다시 살펴야 합니다.
증거에서 수정과 테스트로 이어갑니다
패치를 받아들이기 전에는 재현, 로그, 관련 코드를 연결해 인과관계를 설명하게 합니다. 무엇이 동작을 시작했는지, 어떤 동작이 잘못됐는지, 어떤 보호 장치가 빠졌는지, 그 결과 화면에 어떤 실패가 나타났는지 차례로 확인합니다. 예시의 원인은 API가 느리다는 사실 자체가 아닙니다. 여러 요청이 동시에 진행되는 가운데, 현재 요청인지 확인하지 않고 결과를 적용하는 코드가 문제입니다.
간단한 해결책으로 요청마다 ID를 부여하고, 응답을 반영하기 전에 그 ID가 최신 요청과 일치하는지 검사합니다. 오래된 요청의 결과라면 적용하지 않습니다. 실제 앱에서는 카운터를 관련 컴포넌트나 컨트롤러 안에 두고, 로딩 상태와 오류, 입력값 초기화, 컴포넌트 정리 과정도 확인해야 합니다. AI에게는 오래된 요청이 나중에 끝나는 경우뿐 아니라 최신 요청 실패, 요청 중 입력값 삭제, 요청 도중 컴포넌트 제거 같은 반례도 찾아보게 합니다.
회귀 테스트는 임의의 지연 시간보다 제어된 Promise로 요청 완료 순서를 직접 정하는 편이 낫습니다. 첫 요청과 두 번째 요청을 시작한 뒤 두 번째 요청을 먼저 완료하고, 첫 번째 요청을 나중에 완료합니다. 새 검색이 시작된 뒤에는 이전 응답이 결과를 덮어쓰지 않는다는 요구사항을 검사합니다. 이 테스트가 기존 코드에서는 실패하고 수정 코드에서는 통과해야 원래 실패를 다뤘다는 증거가 됩니다. 테스트가 정의하는 기대 동작은 구현 방식이 아니라 요구사항에서 나와야 합니다.
dev.to 반응
- @mp6nfjxhrlxc — 코드 리뷰에서 ‘즉시 고치기’라는 함정은 매일 봅니다. PR이 증상만 고친 채 병합되고, 근본 원인이 남아 있어 2주 뒤 다른 곳에서 변형된 버그가 돌아옵니다. 효과를 본 방법은 AI가 운영 코드를 한 줄이라도 건드리기 전에 회귀 테스트부터 쓰게 하는 것입니다. “현재 실패하는 테스트를 쓰고, 입력 X가 왜 잘못된 출력 Y를 만드는지 증명한 다음 수정안을 제안하라”고 요청합니다. 재현 테스트를 못 쓰면 AI가 버그를 이해하지 못한 셈입니다. 테스트는 살아 있는 문서가 되고 CI가 회귀를 막습니다. AI를 맹목적으로 믿는 대신 로직을 실행해 보는 러버덕으로 쓰고, 새 테스트로 검증한 뒤 병합합니다.
- @robertadam987_ — 테스트 우선 접근에 동의합니다. 회귀 테스트가 살아 있는 문서가 된다는 점도 좋습니다. 다만 재현에 어려움을 겪는 이유가 환경이나 타이밍 정보 부족일 수도 있습니다. 그때는 AI에게 어떤 증거가 빠졌는지 물어보겠습니다. 비동기 버그에서는 이 흐름을 적용하기가 더 어려웠나요?
- @build996 — 재현 단계에서 짚어야 할 함정이 있습니다.
react에 800ms 지연을 하드코딩하면 가설을 테스트 안에 심는 셈이라, 역순 응답을 확인할 수만 있고 배제할 수는 없습니다. 실제 앱의 START/FINISH/APPLY 기록이 중요한 이유입니다. 인위적인 지연은 그다음에 수정이 버티는지 확인할 때 주로 쓸 만합니다. 실제 계측을 통제된 재현보다 먼저 두도록 순서를 바꾸고 싶습니다. 실제 로그와 AI가 제안한 재현이 달랐던 경우가 있었나요?- @robertadam987_ — 좋은 지적입니다. 인위적인 지연은 메커니즘을 보여줄 뿐 실제 앱에서 그 일이 발생했다는 증거는 아닙니다. 로그를 먼저 두면 그 차이를 더 분명히 할 수 있습니다. 구체적인 사례는 없지만 로그와 재현이 다르면 재현을 맞추기보다 가설을 다시 살펴보겠습니다. 이런 버그에서는 보통 무엇을 먼저 기록하나요?
- @build996 — 응답 순서 문제라면 요청마다 같은 ID를 붙여 세 시점을 기록하겠습니다. 전송 시각, 응답 도착 시각, 결과가 상태에 기록된 시각입니다. 두 요청의 도착 순서가 전송 순서와 다르고 나중에 도착한 요청이 마지막으로 상태를 갱신하는 모습이 보이면 버그를 파악할 수 있습니다. 이 세 기록에 역전이 없다면 경쟁 상태 가설이 틀렸을 가능성이 크므로 재현을 만들기 전에 다른 원인을 살펴야 합니다. 로그가 내 생각과 다른 결과를 보여줄 수 있다는 점이, 하드코딩한 지연과 다릅니다.
- @sinarezaei — 실패를 재현했다고 해서 근본 원인이 자동으로 증명되는 것은 아니라는 점을 강조하고 싶습니다. AI는 로그와 추적 기록에서 패턴을 찾을 수 있지만, 상관관계만으로 인과관계를 입증하지는 못합니다. 경쟁 가설을 시험하고 제안한 수정이 증상만 감추는 게 아니라 실제 메커니즘을 다루는지 확인해야 합니다.
- @uzerkayat — 응답 순서 사례는 디바운싱처럼 그럴듯한 수정이 경쟁 상태를 해결하지 않고 발생 가능 구간만 줄인다는 점을 잘 보여줍니다. 변경을 제안하기 전에 관찰 사실과 가정을 나누게 하면 추측성 수정을 검증 가능한 가설로 바꿀 수 있습니다. 제안된 원인마다 현재 코드에서 실패하고 수정 뒤에는 통과하는 테스트 하나씩 붙이라고 요청하면, 발생 빈도만 낮추는 수정안을 막을 수 있을까요?
- @baumgaerben — 정확한 지적입니다. AI가 lock이나 retry를 계속 제안하는 바람에 race condition을 디버깅하는 데 3일을 쓴 적이 있습니다. 표면만 고치고 상태가 왜 손상되는지 이해하지 못했습니다. “이 상태로 이어지는 실행 경로를 설명하라”고 묻자 AI가 시퀀스 다이어그램을 그렸고, 이벤트가 이미 발생한 뒤 콜백이 리스너를 등록하는 것이 원인임을 알게 됐습니다. “원인을 입증하라”는 프롬프트는 패턴에 맞춘 패치보다 추론을 명시하게 합니다. 코드 리뷰도 쉬워졌습니다. 변경 사항이 여기저기 방어 코드를 추가하는 대신 핵심 로직에 모였습니다.
- @ascendra_ventures_92c21f2 — 수정 전에 증거를 요구하는 습관은 널리 퍼질 만합니다. 원인이 입증되지 않은 수정은 추측이고, AI는 그 추측을 깔끔하게 포장합니다. 클라이언트 시스템에서는 증거가 실패 테스트로 남아 수정 뒤 통과하도록 합니다. 다음 사람이 실행할 수 있는 형태로 원인이 기록되고, 누군가 같은 파일을 다시 생성해도 버그가 조용히 돌아오지 않습니다. 변경 범위도 작아집니다. 원인이 입증되면 보통 한 곳을 고치지만, 그렇지 않으면 패치가 다섯 파일로 퍼지고 회귀가 생기기 쉽습니다. 모델에게 먼저 재현을 요청하나요, 아니면 가설을 세운 뒤 재현을 요청하나요?
원문: dev.to / 번역·요약: Trawling