AI 코드 작성 리스크 — 에러가 아니라 검증 통제 상실을 말하라

← 전체 목차 · 이전: Agent의 답은 맞는데 경로가 위험하면 통과인가 · 다음: 작업이 끝난 뒤 데이터는 어떻게 처리하는가

왜 AI가 쓴 코드를 "돌아갔는지"만 봐서는 안 되는가? 문제를 검증 메커니즘 층위로 격상시켜야 한다.

1층: 의미 보존

모델은 중복 코드를 공통 함수로 뽑아내기를 아주 좋아하지만, 어떤 중복은 의도적으로 남겨둔 것이다.

모델이 구조의 유사성만 보면 "두 개의 업무 도메인을 하나의 범용 로직으로 뒤섞어 버린다".

2층: 경계 조건

  • 널 처리
  • 과거 호환
  • 극단적 수량
  • 동시 쓰기
  • 실패 재시도

3층: 의존 계약

모델은 "에러를 리턴 코드에서 예외 던지기로 바꾸는 것"이 더 우아하다고 여기지만, 호출 측은 줄곧 리턴 코드로 업무 분기를 판단해 왔을 수 있다.

결과: 코드 스타일은 더 우아해지고 규범적이 되었는데 시스템 계약은 파괴되었다.

4층: 테스트 커버리지

  • 테스트는 "이 패치가 무엇을 보호했는가"를 덮어야지 모델이 고친 그 몇 줄만 덮어서는 안 된다
  • 공유 모듈은 특히 회귀 테스트를 보충해 옛 시나리오가 새 작성법의 영향을 받지 않음을 증명해야 한다
  • 한 문장 원칙: 테스트는 업무 결과에 봉사해야지 코드 줄 수에 봉사하면 안 된다.

5층: 무관 변경

AI는 변경 범위를 점점 크게 만든다. 결과적으로 범위가 클수록 의미가 보존되었는지 판단하기 어려워진다.

대책: diff 압축. 매 변경이 하나의 명확한 문제만 해결하게 할 것.

핵심 통찰

  1. AI 프로그래밍의 리스크 중심이 "제대로 못 쓴다"에서 "검증을 못 한다"로 이동했다 — 모델의 문법 정확률은 이미 매우 높고, 남은 리스크는 전부 "표면상 사용 가능하지만 원래 목표를 만족하지 않는" 부류에 집중되어 있으며, 이 부류가 바로 컴파일러와 lint가 전혀 막을 수 없는 것이다.
  2. 위험한 패치는 최적화처럼 생겼다 — 암묵적 오류가 프로덕션에 올라갈 수 있는 이유는 바로 그것이 diff에서 "코드가 깨끗해졌다"로 보이기 때문이다. 리뷰어의 직관이 코드의 미관에 오도된다.
  3. 중복 코드가 반드시 나쁜 냄새인 것은 아니며, 의도적으로 보존된 업무 경계일 수 있다 — "국내 배송비 / 해외 배송비" 예시가 보여주듯 구조가 비슷함 ≠ 업무가 같음. DRY 원칙은 업무 도메인을 가로지를 때 결합 재난을 곧바로 만들어낸다.
  4. 변경의 판정 기준은 "코드가 바뀌었는가"가 아니라 "업무 결정이 바뀌었는가"여야 한다&&||로 바꾸는 것은 한 글자짜리 diff지만 한 번의 요구사항 변경이다. 리뷰 입도는 반드시 텍스트가 아니라 의미까지 내려가야 한다.
  5. 계약에는 암묵 계약이 포함된다 — 인터페이스 문서는 명시적 부분일 뿐이고, "오랜 운영으로 형성된 호출 습관"도 마찬가지로 계약이다. 게다가 문자 기록이 없어서 AI는 전혀 볼 수 없고 사람도 잊기 쉽다. 이것이 AI가 기존 시스템을 수정할 때의 최대 사각지대다.
  6. 테스트의 대상은 줄이 아니라 의미다 — "모델이 고친 그 몇 줄을 덮는 것"은 일종의 가짜 커버리지다. 그 줄들이 실행되었음만 증명할 뿐, 패치가 보호하려던 제약이 여전히 살아 있음을 증명하지 못한다.
  7. Diff 크기 자체가 리스크 지표다 — 범위가 클수록 의미 보존을 판정할 수 없게 된다. 따라서 "diff 압축, 한 번에 하나의 문제만 해결"은 결벽증이 아니라 리뷰가 사람의 인지 대역폭 안에서 완료될 수 있게 하는 전제다.
  8. AI는 악의로 요구사항을 바꾸는 것이 아니라 컨텍스트를 오해하는 것이다 — 이 원인 규명이 대책의 방향을 결정한다: "AI를 불신하기"가 아니라 "컨텍스트를 보충하고 검증 메커니즘을 구축하기"다.

엔지니어링 실전 Tips

  • AI에게 수정 요구를 줄 때 "어떤 중복이 의도적으로 보존된 것인지"를 컨텍스트(주석이나 README)에 써넣을 것. 그러지 않으면 모델은 반드시 그것들을 병합하려 든다.
  • diff를 리뷰할 때 논리 연산자와 조건식의 변화를 우선해서 볼 것. 문자당 리스크가 가장 높은 지점이다.
  • 외부에 노출되는 에러 전달 방식(리턴 코드 / 예외 / 에러 객체)은 공개 API의 일부로 간주해, 어떤 변경이든 리팩터링이 아니라 인터페이스 변경 프로세스를 타게 할 것.
  • 암묵 계약은 능동적으로 명시화할 수 있다: 핵심 인터페이스에 계약 테스트(contract test)를 보충해 "호출 측이 실제로 의존하는 행위"를 고정시키면, AI가 고치는 순간 빨간불이 뜬다.
  • 공유 모듈의 변경은 일률적으로 회귀 테스트를 요구하고, "옛 시나리오가 영향을 받지 않음"을 병합 관문으로 삼을 것.
  • prompt나 도구 설정에서 변경 범위를 명확히 제한할 것("X 함수만 수정하고 이름 변경이나 포맷 변경은 하지 말 것"). 원천에서 diff를 압축한다.
  • "버그 수정"과 "리팩터링"을 두 개의 독립된 커밋/PR로 쪼갤 것. 절대로 AI가 하나의 패치에서 이 두 가지를 동시에 하게 두지 말 것.