AI로 디버깅할 때는 옆자리 동료에게 줄 것을 모델에게 주면 가장 잘 됩니다. 전체 에러, 그 에러를 낸 코드, 기대한 것, 실제로 일어난 일, 이미 배제한 것입니다. 모델은 여러분의 프로그램을 실행할 수도, 데이터를 볼 수도 없으므로, 버그에 대해 아는 모든 것은 여러분이 붙여 넣은 것에서 나옵니다. 가장 쓸모 있는 습관 하나는 수정보다 원인을 먼저 묻는 것입니다.
에러만이 아니라 모든 것을 붙여 넣기
아래 두 탭은 같은 버그를 설명합니다. 엑셀에서 내보낸 CSV 파일을 읽는 스크립트가 파일에 분명히 email 열이 있는데도 KeyError: 'email'로 멈춥니다.
KeyError는 딕셔너리에 'email'이라는 키가 없다는 뜻입니다. 에러를 내는 대신 None을 돌려주는 .get()을 쓰면 에러를 피할 수 있습니다.
email = row.get("email")
또는 먼저 확인하세요.
if "email" in row:
email = row["email"]
첫 번째 답은 KeyError가 무엇인지에 대해서는 맞지만, 여기서는 조언이 해롭습니다. row.get("email")을 쓰면 멈추는 문제는 사라지고, 모든 이메일이 None이 됩니다. 프롬프트가 설명한 것이 그것뿐이었기 때문에 모델은 가장 흔한 버전의 에러에 답했습니다.
전체 보고가 통한 것은 두 줄 덕분입니다. "첫 번째 열만 실패한다"와 "엑셀에서 CSV UTF-8로 저장했다"가 바이트 순서 표시를 가리키는 단서이고, 둘 다 에러 메시지에는 없습니다. 여러분의 세부 사항 중 어느 것이 단서인지는 거의 알 수 없고, 그래서 모두 붙여 넣는 것입니다.
넣어야 할 다섯 가지
- 완전한 에러. 바꿔 말한 것이 아니라 트레이스백이나 콘솔 출력 전체입니다. 줄 번호와 호출 순서는 실패가 어디서 시작됐는지 보여 주는데, 그곳은 에러가 드러난 곳에서 몇 프레임 떨어져 있는 경우가 많습니다.
- 에러를 낸 코드. 실패한 함수와 그 함수를 호출하는 코드입니다. 에러가 어떤 줄을 언급한다면, 그 줄이 붙여 넣은 코드에 있는지 확인하세요.
- 기대한 것과 실제. 각각 한 문장씩. 에러가 전혀 없는 버그(틀린 출력, 빈 페이지, 느린 쿼리)라면 이것이 버그 보고의 전부입니다.
- 실행 환경. 언어 버전, 운영 체제, 그리고 관련될 수 있다면 프레임워크와 라이브러리 버전. 인코딩, 경로, 날짜 버그는 플랫폼에 따라 달라지는 경우가 많습니다.
- 이미 시도한 것. 모델이 같은 것을 다시 제안하지 않게 해 주고, 각 시도의 결과는 증거가 됩니다. "email 부분을 지우면 동작한다"가 CSV 버그를 열 하나로 좁혔습니다.
수정보다 원인을 먼저 묻기
수정을 요청하면 모델은 에러가 사라질 때까지 코드를 바꾸게 됩니다. 원인을 요청하면 에러가 왜 생기는지 추론하게 되고, 수정은 거기서 따라 나옵니다. 각각을 확인할 방법과 함께 순위를 매긴 짧은 원인 목록을 요청하고, 확인은 직접 해 보세요. 아래 템플릿이 그렇게 합니다.
TypeError: Cannot read properties of undefined (reading 'map')function UserList() {
const [users, setUsers] = useState();
useEffect(() => {
fetch("/api/users")
.then((res) => res.json())
.then((data) => setUsers(data));
}, []);
return (
<ul>
{users.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}- 데이터가 생기기 전에 컴포넌트가 렌더링됩니다.
useState()는undefined로 시작하고, React는 이펙트가 실행되기 전에 컴포넌트를 한 번 렌더링하므로undefined에 대해users.map이 호출됩니다. 이것만으로 로드할 때마다 정확히 이 에러가 납니다. 확인:return바로 앞에console.log(users)를 추가하세요.undefined가 찍히고 곧바로 에러가 나면 이것이 원인입니다. - API가 배열이 아닌 것을 돌려줍니다.
{ users: [...] }같은 객체를 돌려줬다면 에러가 달랐을 것이므로(users.map is not a function), 붙여 넣은 에러의 원인은 아닙니다. 그래도 1번을 고친 뒤에 다음 문제가 될 수 있습니다. 확인: 두 번째.then안에서console.log(data). - 요청이 실패합니다. 아직은 관련이 없습니다. 첫 렌더링에서 에러가 나기 때문에 React가 이펙트를 실행하지 않아 요청조차 보내지지 않습니다. 확인: 네트워크 탭에
/api/users요청이 없는데, 이것도 1번을 확인해 줍니다.
이 답변은 수정부터 하는 답이 건너뛰는 일을 합니다. 어떤 원인이 낼 에러가 붙여 넣은 에러와 다르거나, 코드가 그 원인이 생길 만큼 진행되지 않는다는 이유로 원인을 배제합니다. 에러의 정확한 문구가 중요한 이유도 이것입니다. 답하기 전에 원인을 나열하게 하는 것은 가벼운 형태의 생각의 사슬(CoT) 프롬프팅입니다. 추론이 먼저 오고 결론은 그 위에 섭니다.
최소 재현 코드 만들기
최소 재현 코드는 버그가 여전히 나타나는 가장 작은 프로그램입니다. 데이터베이스 호출 대신 하드코딩한 데이터, 모듈 전체 대신 함수 하나입니다. 이것을 만들다 보면 누군가에게 묻기 전에 버그를 찾는 경우가 많습니다. 조각을 하나 뺄 때마다 버그가 남거나(관련 없음) 사라지기(관련 있음) 때문입니다. 그래도 못 찾았다면 재현 코드가 이상적인 프롬프트입니다. 모델이 모든 줄을 읽을 만큼 짧고, 엉뚱한 문제를 쫓게 만들 관계없는 코드도 없습니다.
버그가 데이터에 달려 있다면 버그를 일으키는 행 몇 개를 넣으세요. 모델은 [{"id": 1, "name": null}]에 대해서는 추론할 수 있지만, "프로덕션의 어떤 행들"에 대해서는 추론할 수 없습니다.
수정이 더 이상 통하지 않을 때
모델의 세 번째 수정이 같은 방식으로 실패했다면, 네 번째 수정 요청이 더 나을 가능성은 낮습니다. 두 가지가 더 도움이 됩니다.
- 새로운 증거를 주세요. 실패 지점의 실제 값을 보여 주는 print나 로그 줄을 추가하고, 실행해서 출력을 붙여 넣으세요. 모델의 가설과 모순되는 증거가 더 나은 가설로 가는 가장 빠른 길입니다.
- 새 대화를 시작하세요. 긴 디버깅 대화는 버린 가설과 예전 버전의 코드로 가득 차고, 모델은 그 위에 계속 쌓을 수 있습니다. 현재 코드, 에러, 증거, 그리고 "이미 배제함: X와 Y"라는 줄로 새 대화를 시작하면 답변 한 번에 더 멀리 가는 경우가 많습니다.
라이브러리 동작에 대한 자신만만한 답은 조심하세요. 모델은 존재하지 않는 옵션이나 함수를 설명할 수 있습니다. 확인하는 방법은 AI 할루시네이션을 참고하세요. 코드가 고장 난 것은 아닌데 왜 그렇게 동작하는지 이해가 안 된다면 코드 설명 프롬프트가 더 알맞은 도구입니다.
자주 묻는 질문
ChatGPT나 Claude에게 코드 수정을 어떻게 요청하나요?
전체 에러 메시지와 그 에러를 낸 코드를 붙여 넣고, 기대한 것, 실제로 일어난 일, 이미 시도한 것을 짧게 세 줄로 더하세요. 수정을 요청하기 전에 가장 가능성 높은 원인과 그것을 확인하는 방법을 물어보세요. 그냥 "이거 고쳐 줘"라고 하면 가장 흔한 버전의 에러에 대한 수정이 나오는데, 그것이 여러분의 에러가 아닐 수 있습니다.
프로젝트 전체를 AI에 붙여 넣어야 하나요?
아니요. 에러가 나는 함수, 그 함수를 호출하는 코드, 함수가 받는 데이터 샘플을 붙여 넣으세요. 더 좋은 방법은 문제를 여전히 재현하는 가장 작은 프로그램으로 줄이는 것입니다. 관계없는 파일은 답을 느리게 하고, 모델이 있지도 않은 문제를 찾아 헤맬 곳만 늘립니다.
AI의 수정으로 에러는 사라졌는데 왜 프로그램은 여전히 안 되나요?
수정이 증상만 치료했기 때문입니다. 예를 들어 row["email"]을 row.get("email")로 바꾸면 KeyError는 멈추지만, 앞 단계의 버그 때문에 키가 없는 것이라면 이제 모든 이메일이 조용히 None이 됩니다. 원인과 그 확인 방법을 먼저 물으면 문제를 숨기기만 하는 수정을 피할 수 있습니다.
AI가 계속 안 되는 수정만 제안하면 어떻게 하나요?
수정 요청을 멈추고 대신 증거를 주세요. 각 시도가 무엇을 바꿨는지 알려 주고, 실제 값을 보여 주는 print나 로그 줄을 추가해서 그 출력을 붙여 넣으세요. 대화가 길어졌다면 코드, 에러, 증거, 이미 배제한 수정을 깔끔하게 정리해서 새 대화를 시작하세요.