Chapter 5

생성된 코드의 이해

  • 5.1 의도에서 구현으로
  • 5.2 코드 가독성
  • 5.3 디버깅 전략
  • 5.4 리팩터링
  • 5.5 테스트

AI가 생성한 코드를 그냥 쓰지 말고 이해해야 해. 바이브 코딩에서 가장 흔한 실수가 "돌아가니까 넘어가자"인데, 이게 기술 부채를 기하급수적으로 쌓는 지름길이거든.

프롬프트로 의도를 전달하면 AI가 구현을 생성하잖아. 여기서 의도와 구현 사이의 간극을 이해하는 게 핵심이야.

같은 의도를 전달해도 AI는 매번 다른 구현을 생성할 수 있어. "정렬 함수를 만들어줘"라고 하면 퀵소트를 줄 수도 있고, 머지소트를 줄 수도 있고, 내장 sort 함수를 감싼 래퍼를 줄 수도 있지. 어떤 게 맞는지는 맥락에 따라 다르고, 그 판단은 개발자의 몫이야.

제안하는 프로세스는 이래:

  1. 생성된 코드를 일단 읽어 — 당연한 것 같지만 실제로 안 하는 사람이 많아
  2. AI에게 설명을 요청해 — "이 코드가 어떻게 동작하는지 단계별로 설명해줘"
  3. 대안을 요청해 — "다른 방식으로 구현할 수 있는 방법이 있어?"
  4. 트레이드오프를 이해해 — "이 구현의 장단점은 뭐야? 성능, 가독성, 메모리 측면에서"

이 과정을 거치면 AI가 생성한 코드가 블랙박스에서 이해 가능한 코드로 바뀌지. 시간이 걸리지만 장기적으로는 유지보수 비용을 아끼는 투자야.

AI가 생성하는 코드의 가독성 문제는 독특해. 인간이 쓴 코드와는 다른 패턴으로 읽기 어려운 경우가 있거든.

AI 생성 코드의 가독성 문제들을 보면:

  • 과도한 추상화 — AI가 "좋은 코드"를 만들려고 불필요한 계층을 추가하는 경우. 간단한 기능인데 팩토리 패턴 위에 전략 패턴 위에 데코레이터 패턴을 쌓는 식
  • 비일관적인 스타일 — 같은 프로젝트 안에서 여기는 async/await, 저기는 콜백, 또 저기는 Promise 체이닝. AI에게 각각 별도로 요청하면 이런 일이 생겨
  • 주석 과다 또는 부재 — AI가 모든 줄에 주석을 다는 경우도 있고, 아무 설명 없이 복잡한 로직을 생성하는 경우도 있지
  • 매직 넘버 — 의미 없는 숫자가 코드에 하드코딩되어 있는 경우. AI가 맥락을 알고 넣은 건지, 그냥 넣은 건지 구분이 안 돼

해결책은 프롬프트에 코딩 스타일을 명시하는 거야. "ESLint airbnb 설정을 따르고, 함수는 20줄 이내로, 변수명은 의미를 담아서, 주석은 '왜'만 적어"처럼. 그리고 생성 후에 팀의 린트 규칙을 적용해서 스타일을 통일하면 돼.

AI가 생성한 코드에서 버그가 나면 디버깅이 평소보다 어려워. 이유는 간단해 — 자기가 짠 코드가 아니니까. 코드의 의도를 이해하지 못한 상태에서 디버깅하면 시간이 배로 걸리거든.

AI 코드 디버깅 전략을 보면:

1단계: 에러를 AI에게 보여줘

에러 메시지, 스택 트레이스, 관련 코드를 AI에게 통째로 던지면 높은 확률로 원인을 찾아줘. 단순한 타입 에러, 오탈자, import 누락 같은 건 AI가 순식간에 잡지.

2단계: 재현 조건을 좁혀

AI가 못 잡는 버그는 보통 재현 조건이 복잡한 것들이야. 이때는 인간이 직접 재현 조건을 좁혀서 **최소 재현 사례(minimal reproduction)**를 만든 후 AI에게 주는 게 효과적이지.

3단계: 가정을 검증해

AI가 "이게 원인일 것 같다"고 말하면 무조건 믿지 말고, 실제로 그 부분을 바꿔서 버그가 사라지는지 확인해. AI가 그럴듯하게 설명하지만 실제 원인과 다른 경우가 꽤 있거든.

4단계: 전통적인 디버깅 기법을 병행해

console.log, 브레이크포인트, 프로파일러 같은 도구를 활용해. AI 시대라고 해서 전통적인 디버깅 기법이 무용해진 게 아니야. 오히려 AI가 생성한 코드를 검증할 때 더 필요하지.

AI가 생성한 코드를 리팩터링하는 것도 독특한 도전이야. AI가 생성한 코드는 처음부터 리팩터링이 필요한 경우가 많거든.

AI는 "동작하는 코드"를 만드는 데 최적화되어 있지, "유지보수하기 좋은 코드"를 만드는 데 최적화되어 있지 않아.

AI를 활용한 리팩터링 전략:

  • 함수 추출 — "이 함수가 너무 긴데, 의미 단위로 작은 함수로 나눠줘"
  • 네이밍 개선 — "이 코드에서 변수명과 함수명을 더 의미 있게 바꿔줘"
  • 중복 제거 — "이 파일에서 중복되는 로직을 찾아서 공통 함수로 추출해줘"
  • 패턴 적용 — "이 분기문을 전략 패턴으로 리팩터링해줘"

주의할 점 — 리팩터링을 AI에게 시킬 때 테스트가 먼저 있어야 해. 테스트 없이 리팩터링하면 동작이 바뀌었는지 확인할 방법이 없잖아. 이건 AI 코드든 인간 코드든 마찬가지지만, AI 코드에서는 원래 의도를 정확히 모르니까 더 중요하지.

테스트 얘기를 좀 더 깊이 해야 해. AI가 생성한 코드에 대한 테스트는 선택이 아니라 필수야.

AI를 활용한 테스트 전략을 보면:

테스트 코드도 AI로 생성하되, 검증은 직접 해. "이 함수에 대한 유닛 테스트를 작성해줘. 정상 케이스 3개, 엣지 케이스 3개, 에러 케이스 2개를 포함해"라고 요청하면 합리적인 테스트를 만들어줘. 하지만 AI가 생성한 테스트가 정말 의미 있는 케이스를 커버하는지는 확인해야 해.

AI 생성 테스트의 흔한 함정:

  • 구현 세부사항을 테스트하는 깨지기 쉬운(brittle) 테스트
  • 항상 성공하는 무의미한 테스트 (입력과 기대 출력이 같은 값)
  • 핵심 엣지 케이스를 놓치는 테스트

TDD와 AI의 결합: 추천하는 워크플로가 있어 — 먼저 테스트를 직접 작성하거나 AI에게 요청해서 만들고, 그 테스트를 통과하는 구현을 AI에게 시키는 거야. 이러면 AI의 출력이 테스트라는 안전망 안에서 생성되니까, 70% 문제의 일부를 해결할 수 있지.


정리

5장에서 기억할 거 세 가지:

  1. AI가 생성한 코드를 이해하지 않고 쓰면 기술 부채가 기하급수적으로 쌓여. "돌아가니까 넘어가자"는 가장 위험한 습관이야
  2. 디버깅에서 AI는 도우미지 해결사가 아니야. 단순한 에러는 AI가 잡지만, 복잡한 버그는 전통적인 디버깅 기법과 병행해야 해
  3. 테스트를 먼저 만들고 구현을 AI에게 시키는 TDD 방식이 효과적이야. 테스트가 안전망 역할을 해서 AI 생성 코드의 품질을 보장해주거든