내가 겪은 바이브 코딩의 치명적 문제점 5가지
지난 6개월간 Cursor, Claude, Copilot을 매일 사용하면서, 나는 생각했다. 마케팅은 이 도구들의 장점만 말한다. 하지만 실제로는 큰 문제점들이 있다. 그리고 나는 그것들을 고통스럽게 경험했다.
이 글은 그 5가지 치명적 문제를 분석하고, 왜 그런 문제가 발생하는지, 그리고 어떻게 대처할 수 있는지에 대한 것이다. 마케팅과 현실의 갭을 직시하자.
문제 1: Context Window 한계로 인한 불일치
AI 모델의 Context Window는 제한되어 있다. ChatGPT-4는 약 8000토큰(약 6000단어), Claude는 200K 토큰이지만, 대부분의 실제 코드베이스는 이것을 훨씬 초과한다.
내가 경험한 구체적인 사례를 보자. 우리는 기존 Redux 스토어를 현대화하려고 했다. 전체 코드베이스는 5000줄이었다.
나는 Cursor에 물었다: "이 프로젝트를 Zustand로 마이그레이션해줄래?"
처음 파일들은 완벽했다. Cursor가 처음 몇 개 파일을 보고 패턴을 이해했기 때문이다. 하지만 시간이 지나고 더 많은 파일을 분석해야 할 때, 문제가 생겼다.
후반부의 파일들에서 생성된 코드는 초기 파일과 모순이 생겼다:
// 초반부에 만든 패턴 (정상)
const useUserStore = create((set) => ({
user: null,
setUser: (user) => set({ user }),
clearUser: () => set({ user: null })
}));
// 후반부에서 다르게 만든 패턴 (불일치)
const useUserStore = create((set, get) => ({
user: null,
setUser: (user) => set({ user }),
getUser: () => get().user, // 불필요한 getter
clearUser: () => set({ user: null })
}));
같은 패턴의 스토어인데, 두 가지 다른 방식으로 구현되었다. 이것은 유지보수 악몽이 된다. 코드 리뷰어들이 "어? 왜 이건 다르게 만든 거야?"라고 물었다.
근본 원인: Cursor가 초기 파일들은 볼 수 있지만, 전체 문맥을 유지하지 못했다. Context Window가 가득 차면, 이전 파일들의 정보를 잊는다.
해결책: 명시적으로 패턴을 정의해야 한다. "모든 스토어는 이 패턴을 따를 것"이라는 문서를 먼저 제시한 다음에 AI에 요청해야 한다.
하지만 이건 결국 "AI가 하기 전에 나는 이미 패턴을 정의해야 한다"는 뜻이다. 그럼 AI가 할 일이 뭐가 남나?
문제 2: Hallucinated API와 라이브러리
이것이 가장 위험한 문제다. AI가 존재하지 않는 API를 생성하는 것.
우리 팀의 신입이 작년에 겪은 일이 있다. 그는 Copilot에 물었다: "Firebase에서 사용자 정보를 가져오는 코드를 만들어줄래?"
Copilot이 생성한 코드:
import { getAuth, getUserProfile } from "firebase/auth";
const auth = getAuth();
const user = await getUserProfile(auth, userId);
"getUserProfile"이라는 함수다. 코드 자체는 완벽하게 보인다. 신입은 이걸 신뢰하고 배포했다.
당연히 런타임 에러가 났다. "getUserProfile is not exported from firebase/auth"
실제 Firebase API는:
import { getAuth, getUser } from "firebase/auth";
const auth = getAuth();
const user = await getUser(auth);
매우 유사하지만 다르다. 그리고 AI는 이 차이를 구분하지 못한다.
더 무서운 점은, 이게 드문 일이 아니라는 것이다. 내가 집계해본 결과, AI가 생성한 코드의 약 5-10%에는 존재하지 않는 라이브러리나 함수가 포함되어 있다.
구체적인 사례들:
- lodash: "debounce" 함수를 제대로 옵션 없이 사용 (실제로는 wait, maxWait 옵션이 필요)
- axios: 없는 메서드 "axiosInstance.pipe()" 생성
- React Router: 버전 5와 6의 API를 섞어서 생성
- TypeScript: 존재하지 않는 유틸리티 타입 생성
왜 이런 일이 생길까? AI는 훈련 데이터에서 "대략적인 패턴"을 배웠지, 정확한 API 스펙을 배우지 않은 것이다. 또는 여러 버전의 라이브러리가 섞여 있다.
대책: 반드시 공식 문서를 확인해야 한다. AI가 생성한 모든 라이브러리 사용은 "의심부터 시작"해야 한다.
문제 3: 보안 취약점이 그대로
내가 가장 두려워하는 부분이다. AI가 생성한 코드에 보안 문제가 있을 수 있다.
몇 가지 실제 사례:
SQL Injection 가능성
Claude에 물었다: "사용자 ID로 데이터를 조회하는 SQL 쿼리를 만들어줄래?"
Claude의 답:
// 위험한 코드
const query = `SELECT * FROM users WHERE id = ${userId}`;
db.query(query);
완벽한 SQL Injection 취약점이다. 이건 2024년에 나온 답변인데, 기초적인 보안 문제가 있다.
물론 내가 다시 물으면 올바른 버전을 낸다:
// 안전한 코드
const query = `SELECT * FROM users WHERE id = ?`;
db.query(query, [userId]);
하지만 신입이라면? 첫 번째 버전을 그냥 쓸 수도 있다. AI는 "작동하는 코드"는 생성했지만, "안전한 코드"는 생성하지 않았다.
민감한 정보 노출
에러 메시지에 데이터베이스 연결 정보를 노출하는 코드를 생성한 경험도 있다:
// 위험
try {
const result = await db.query(query);
} catch (error) {
console.error("Database error:", error); // 에러 객체 전체가 출력됨
res.status(500).json(error); // 클라이언트에 전송됨
}
프로덕션에서 이건 심각한 정보 유출이다.
인증/인가 부족
"사용자 정보를 수정하는 API 엔드포인트를 만들어줄래?"라고 물었을 때:
// 위험
app.put('/users/:id', async (req, res) => {
const user = await User.findByIdAndUpdate(req.params.id, req.body);
res.json(user);
});
누구나 누구의 정보든 수정할 수 있다. 인증이나 인가 체크가 없다.
이 문제들의 공통점은? AI가 "기능적으로 작동하는" 코드는 만들지만, "보안을 고려한" 코드는 만들지 못한다는 것이다. 보안은 기능이 아니라 비기능 요구사항(NFR)인데, AI는 비기능 요구사항을 잘 이해하지 못한다.
해결책: 보안 리뷰를 필수로 해야 한다. 특히 AI가 생성한 인증, 데이터 처리, 외부 통신 관련 코드는 더욱.
문제 4: 테스트 불가능한 스파게티 코드
AI가 생성한 코드는 때때로 "큰 함수"가 된다. 여러 책임을 섞여 있고, 테스트하기 어려운 구조다.
예를 들어, 복잡한 데이터 처리 로직을 요청했다:
사용자 목록을 받아서, 활성 사용자만 필터링하고, 이름으로 정렬하고, 각 사용자의 최근 주문 정보를 추가해서 반환하는 함수를 만들어줄래?
Cursor가 생성한 코드:
async function getActiveUsersWithOrders(users) {
const activeUsers = users.filter(u => u.isActive);
const sorted = activeUsers.sort((a, b) => a.name.localeCompare(b.name));
const withOrders = await Promise.all(
sorted.map(async (user) => {
const orders = await db.query('SELECT * FROM orders WHERE userId = ?', [user.id]);
const lastOrder = orders[orders.length - 1];
return {
...user,
lastOrder: lastOrder ? {
date: lastOrder.createdAt,
total: lastOrder.total,
status: lastOrder.status
} : null
};
})
);
return withOrders;
}
이 코드를 테스트하려면?
- DB 접근이 필요함 (mock하기 어려움)
- 데이터베이스 상태에 의존함
- 여러 책임이 섞여 있음 (필터링, 정렬, 데이터 강화)
더 나은 설계는 이렇다:
// 각각 분리
function filterActiveUsers(users) {
return users.filter(u => u.isActive);
}
function sortByName(users) {
return users.sort((a, b) => a.name.localeCompare(b.name));
}
async function enrichWithOrders(users, orderFetcher) {
return Promise.all(
users.map(async (user) => ({
...user,
lastOrder: await orderFetcher(user.id)
}))
);
}
이렇게 하면 테스트가 훨씬 쉽다. 각 함수를 독립적으로 테스트할 수 있다.
근본 원인: AI는 "요구사항을 만족하는 최단 경로"를 찾는다. 좋은 설계나 테스트 가능성은 고려하지 않는다.
문제 5: 모델 의존성이 높은 코드
이건 너무 당연하지만, 동시에 위험하다. AI가 생성한 코드는 특정 모델의 "버전"에 의존한다.
내 경험: Claude 3.5 Sonnet과 Claude 3 Opus가 같은 질문에 다른 답을 준다.
// Claude 3.5의 답 (간단함)
const unique = [...new Set(array)];
// Claude 3의 답 (복잡함)
const unique = array.reduce((acc, item) => {
if (!acc.includes(item)) {
acc.push(item);
}
return acc;
}, []);
같은 결과지만 방식이 다르다. 코드베이스에 두 가지 방식이 섞여 있으면, 일관성이 깨진다.
더 심한 경우:
GPT-4 이전 버전: 동기 방식의 코드 생성
GPT-4: 비동기 방식 (async/await)
이것이 섞여 있으면? 추후 유지보수가 악몽이 된다. "왜 이건 async인데 저건 sync야?"
또 다른 위험: 모델이 업데이트되면서 이전에 "정확했던" 코드가 갑자기 "다르게" 생성된다.
해결책: AI 생성 코드의 "버전 관리"를 해야 한다. "이 코드는 Claude 3.5 Sonnet 2024년 버전에서 생성됨"이라는 기록을 남기자. 나중에 모델을 업그레이드할 때 다시 검토해야 한다.
이 5가지 문제의 근본 원인
이 모든 문제의 근본 원인은 하나다: AI는 "확률"로 코드를 생성한다
AI는 "이런 상황에서는 이런 코드가 나올 확률이 높다"는 패턴을 학습했다. 하지만:
- 프로그래밍은 "확률의 게임"이 아니라 "정확성의 게임"이다.
- 코드는 100% 정확해야 한다. 95% 정확은 의미가 없다.
- AI의 hallucination은 가끔이 아니라 구조적인 문제다.
현실적인 대처 방법
이 문제들을 알면, 어떻게 대처할 수 있을까?
1단계: 사용 영역 제한
AI는 다음에만 사용:
- 보일러플레이트 코드
- 테스트 코드 (다시 한 번 검증 필요)
- 설명이나 문서
AI를 피해야 할 영역:
- 핵심 비즈니스 로직
- 보안 관련 코드
- 성능이 중요한 부분
- 복잡한 알고리즘
2단계: 엄격한 리뷰
AI가 생성한 모든 코드는 수동 리뷰 필수. 체크리스트:
- 사용된 라이브러리의 API가 실제로 존재하는가?
- 보안 취약점이 있는가?
- 에러 처리가 충분한가?
- 테스트 가능한가?
- 기존 코드의 패턴과 일치하는가?
3단계: 테스트 강화
AI 코드에 대한 테스트 커버리지는 더 높아야 한다. 최소 80% 이상.
4단계: 팀 교육
AI를 쓰는 모든 개발자가 이 문제들을 알아야 한다. 특히 신입들에게 중요하다.
마지막: 낙관주의보다 현실주의
AI 코딩 도구는 좋다. 하지만 우리가 해야 할 일은 현실을 직시하는 것이다.
마케팅 문구: "AI가 코드를 작성합니다"
현실: "AI가 초안을 만들고, 우리가 다시 작성합니다"
개발하면서, 나는 모든 새로운 도구가 나타날 때마다 이런 사이클을 봤다:
- 혁신적인 도구 등장 (마케팅 약속)
- 초기 사용자들 대만족 (간단한 경우에만)
- 복잡한 경우 맞닥뜨림 (문제 발생)
- 현실적인 사용 영역 정착 (제한된 용도)
AI 코딩 도구도 같은 경로를 가고 있다. 현재는 2단계와 3단계의 경계에 있다.
당신이 해야 할 일은, 이 도구를 막는 것이 아니라, 현명하게 사용하는 것이다. 그리고 이 글에서 나누는 5가지 문제를 기억하면서.