Vibe‑Coding 함정: AI가 만든 기능은 설계가 아니다

많은 개발자에게 LLM을 사용해 프로젝트를 시작한 첫 몇 주는 마치 초능력을 얻은 듯한 느낌을 줍니다. 기능을 프롬프트하면 바로 나타나고, 동작하며, 생산성이 급상승합니다. 이것을 vibe‑coding이라고 부릅니다. 아이디어와 실제 구현 사이의 거리가 자연어 몇 문장으로 줄어드는 흐름 상태죠.

하지만 프로젝트가 커지면서 눈에 보이지 않는 비용이 쌓이기 시작합니다. 최근 글에서 k10s—GPU‑인식 Kubernetes 대시보드의 제작자는 자신들의 vibe‑coded 프로젝트가 무너진 순간을 상세히 설명했습니다. 7개월 동안 빠르게 기능을 제공한 뒤, AI가 요청된 모든 기능을 구현했지만 그 과정에서 애플리케이션의 기반을 침식했다는 사실을 발견했습니다. 그 결과는 유지보수가 불가능한 1,690줄짜리 god object가 되었습니다.

속도의 착각

k10s를 만들면서 저자는 초기 "고양이"를 경험했습니다. 포드 뷰, 로그 스트리밍, 특수 GPU 플릿 뷰 같은 기능이 단일 세션으로 제공되었습니다. 속도는 중독적이었으며, 수작업 코딩보다 대략 10배 빠른 수준이었습니다.

하지만 이 속도는 착각에 불과했습니다. AI는 즉각적인 프롬프트에 최적화되기 때문에 가장 짧은 경로로 동작 가능한 솔루션을 선택합니다. Go와 Bubble Tea로 만든 복잡한 TUI(터미널 사용자 인터페이스)에서는 모든 새로운 기능을 하나의 전역 상태 구조체에 억지로 끼워넣는 결과를 낳았습니다. AI는 장기적인 유지보수성을 전혀 고려하지 않았고, 오직 사용자가 엔터를 치는 순간 FleetView가 동작하는 것만 신경썼습니다.

파멸에서 얻은 다섯 가지 원칙

k10s의 붕괴를 통해 저자는 AI를 활용해 진지한 소프트웨어를 구축하려는 모든 사람에게 적용할 수 있는 다섯 가지 핵심 교훈을 도출했습니다. 이는 단순 팁이 아니라 프로젝트 설정(CLAUDE.md 혹은 agents.md 파일 등)에 명시적으로 정의해 AI가 최소 저항 경로를 택하지 못하도록 하는 가드레일입니다.

1. AI는 기능을 만든다, 설계를 만들지는 않는다

AI는 특정 요구사항을 구현하는 데는 뛰어나지만, 시스템 전체의 비전을 유지하는 데는 부족합니다. 제약이 없으면 모든 새로운 기능을 특수 케이스로 처리합니다.

"속도가 당신을 승리하고 있다고 착각하게 만들지만, 모든 것이 동시에 무너지는 순간이 찾아옵니다."

The Fix: 프롬프트하기 전에 아키텍처—인터페이스, 메시지 타입, 소유권 규칙—를 직접 정의하세요. 새로운 뷰를 추가할 때 기존 뷰를 수정해서는 안 된다는 점을 AI에게 명확히 알려줍니다.

2. God Object는 AI가 기본적으로 만들어내는 산물

LLM은 모든 것을 하나의 구조체에 담는 방향으로 기울어집니다. 이는 최소한의 관례만 요구하기 때문이며, 결과적으로 단일 키바인딩(예: s 키)이 현재 뷰에 따라 세 가지 다른 동작을 수행하는 flat dispatch 악몽을 초래합니다.

The Fix: 엄격한 상태 소유권을 강제하세요. 각 뷰는 특정 인터페이스를 구현하는 별도 구조체여야 하며, 뷰‑전용 상태가 전역 애플리케이션 모델에 들어가서는 안 됩니다.

3. 속도 착각은 범위를 넓힌다

구현이 "자유롭다"고 느껴지면 개발자는 상상할 수 있는 모든 기능을 추가하고 싶어집니다. 저자는 원래 목표였던 GPU 전용 도구 대신, 일반적인 Kubernetes TUI(사실상 k9s 복제)를 만들게 되었습니다.

The Fix: 엄격한 비전 문서를 유지하세요. 도구가 무엇인지뿐 아니라 누구를 위한 것이 아닌지도 명시합니다. 이러한 경계를 AI 컨텍스트에 포함시켜, 생성이 쉬워서 발생하는 범위 확장을 방지합니다.

4. 위치 데이터는 시간 폭탄이다

테이블을 빠르게 렌더링하기 위해 AI는 구조화된 데이터를 간단한 문자열 배열([]string)로 평탄화하는 경우가 많습니다. 이는 매직 넘버(예: row[3]이 "Alloc" 열)를 만들고, 열 순서가 조금만 바뀌어도 정렬 및 렌더링 로직 전체가 조용히 깨집니다.

The Fix: 구조화된 데이터의 평탄화를 금지합니다. 최종 렌더 호출 전까지는 데이터가 타입이 지정된 구조체 형태로 흐르도록 요구하고, 식별에는 배열 인덱스가 아니라 필드 이름을 사용합니다.

5. AI는 상태 전이를 소유하지 않는다

동시성 환경에서 AI는 종종 클로저나 goroutine 내부에서 상태를 직접 변형해 올바른 메시지 전달 과정을 생략합니다. 이는 거의 디버깅이 불가능한 간헐적인 데이터 레이스를 초래합니다.

The Fix: 렌더링에 보이는 모든 상태 변형은 메인 이벤트 루프에서만 일어나도록 강제합니다. 백그라운드 워커는 데이터를 생성하고, 이를 타입이 지정된 메시지로 전송해 원자적으로 적용하도록 해야 합니다.

커뮤니티 평결: 도구인가, 지팡인가?

이 이야기는 Hacker News에서 엔지니어들 사이에 뜨거운 논쟁을 일으켰으며, AI가 개발 워크플로우에서 어떻게 인식되는가에 대한 근본적인 갈등을 드러냈습니다.

회의론자들은 저자의 경험이 감독 부재의 결과라고 주장했습니다. 많은 이들은 시니어 엔지니어라면 코드를 직접 읽지 않은 채로는 절대 배포해서는 안 된다고 강조했으며, 한 댓글은 이렇게 적었습니다:

"소프트웨어 엔지니어링에서 가장 어려운 부분은 코드를 쓰는 것이 아니라… 모든 다른 일이다."

실용주의자들은 중간 지점을 제시했습니다. AI는 "지루한" 구현 세부 사항을 담당하고, 인간은 고수준 설계와 엄격한 리뷰를 담당한다는 접근법입니다. 그들은 AI를 "과시된 코드 생성기"라고 부르며, 설계 무결성에 대한 책임은 100% 인간에게 있다고 주장했습니다.

AI 낙관주의자들은 해결책이 단순히 더 나은 프롬프트와 보다 정교한 에이전트 워크플로우라고 주장했습니다. 저자는 파괴된 코드를 처음부터 다시 쓰는 대신 AI를 활용해 리팩터링했어야 한다는 의견이었습니다.

결론: 설계로의 복귀

저자는 이제 k10s를 Rust로 다시 작성하고 있습니다. 이는 언어 자체의 우수성 때문이 아니라, "조종할 수 있는" 언어이기 때문입니다. 변화는 "vibe‑coding"에서 "설계‑우선 코딩"으로 전환되는 과정입니다.

현대 개발자에게 주는 교훈은 명확합니다. AI는 코드 라인당 한계 비용을 사실상 0으로 낮출 수 있지만, 복잡성 비용을 낮추지는 못합니다. AI의 속도를 견디는 유일한 방법은 AI가 아직 따라올 수 없는 역량—아키텍처 판단, 엄격한 경계 설정, 그리고 "쉽게 만들 수 있다"는 이유만으로 기능을 거절하는 훈련—에 집중하는 것입니다.

Sources