내가 겪은 바이브 코딩의 치명적 문제점 5가지

게시일: 2026년 2월 10일 · 17분 읽기

지난 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%에는 존재하지 않는 라이브러리나 함수가 포함되어 있다.

구체적인 사례들:

왜 이런 일이 생길까? 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;
}
      

이 코드를 테스트하려면?

더 나은 설계는 이렇다:

// 각각 분리
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는 "이런 상황에서는 이런 코드가 나올 확률이 높다"는 패턴을 학습했다. 하지만:

현실적인 대처 방법

이 문제들을 알면, 어떻게 대처할 수 있을까?

1단계: 사용 영역 제한

AI는 다음에만 사용:

AI를 피해야 할 영역:

2단계: 엄격한 리뷰

AI가 생성한 모든 코드는 수동 리뷰 필수. 체크리스트:

3단계: 테스트 강화

AI 코드에 대한 테스트 커버리지는 더 높아야 한다. 최소 80% 이상.

4단계: 팀 교육

AI를 쓰는 모든 개발자가 이 문제들을 알아야 한다. 특히 신입들에게 중요하다.

마지막: 낙관주의보다 현실주의

AI 코딩 도구는 좋다. 하지만 우리가 해야 할 일은 현실을 직시하는 것이다.

마케팅 문구: "AI가 코드를 작성합니다"

현실: "AI가 초안을 만들고, 우리가 다시 작성합니다"

개발하면서, 나는 모든 새로운 도구가 나타날 때마다 이런 사이클을 봤다:

  1. 혁신적인 도구 등장 (마케팅 약속)
  2. 초기 사용자들 대만족 (간단한 경우에만)
  3. 복잡한 경우 맞닥뜨림 (문제 발생)
  4. 현실적인 사용 영역 정착 (제한된 용도)

AI 코딩 도구도 같은 경로를 가고 있다. 현재는 2단계와 3단계의 경계에 있다.

당신이 해야 할 일은, 이 도구를 막는 것이 아니라, 현명하게 사용하는 것이다. 그리고 이 글에서 나누는 5가지 문제를 기억하면서.

iL
ian.lab

실무 개발자입니다. 현장에서 겪은 문제와 해결 과정을 기록합니다. 오류 제보는 연락처로 보내주세요.