vibe coding은 사람이 의도를 말로 던지고 LLM이 code를 채우는 방식이다. 1편에서 prompt 한 번의 한계를 셋으로 적었다. 상태가 없고, 도구가 없고, 검증이 없다. code 쪽에서 그 한계를 처음 흔든 게 vibe coding이다. 이름은 2025년 2월 Andrej Karpathy가 쓴 글에서 왔다 [1]. “forget that the code even exists”, “fully give in to the vibes”.
‘LLM 활용의 진화’ 시리즈 ②편. ① Prompting · ② Vibe coding · ③ Agents · ④ Harness engineering · ⑤ Open models.

그림 1. 생성 뒤에 격리·실행·검증 loop를 붙이면 vibe coding의 속도를 유지하면서 깨지는 지점을 앞당길 수 있다.
나도 이 방식으로 일을 많이 한다. 혼자 backend부터 app까지 만들어야 하는 상황에서 vibe coding이 준 속도는 무시할 수 없는 크기였다. 동시에 이 방식은 “분명히 돌아갔는데”로 시작하는 장애를 만들기 쉽다. 그래서 이 글은 vibe coding을 칭찬하거나 경계하는 글이 아니라, 그 속도를 유지한 채 어디에 검증을 끼워 넣어야 하는지에 대한 메모다.
vibe coding이 없앤 것: 만드는 마찰
vibe coding이 바꾼 건 추상화 수준을 한 칸 올린 것이다. 전에는 한 줄씩 직접 썼다면, 이제는 “이런 걸 만들고 싶다”는 의도를 말하고 LLM이 code를 채운다. 사람은 결과를 보고 다시 말로 고친다.
여기서 두 가지가 풀렸다. 하나는 속도다. prototype 하나 만드는 시간이 몇 시간에서 몇 분으로 줄었다. 다른 하나는 접근성이다. 문법을 다 외우지 않아도 의도가 분명하면 무언가를 만들 수 있게 됐다. 만드는 일 자체의 마찰이 크게 줄었다.
느낌은 검증이 아니다
문제는 vibe coding이 prompting의 한계 하나를 그대로 물려받았다는 데 있다. 검증이 없다. 돌아가는 것처럼 보이는 code가 맞는 code라는 보장은 없다.
- 동작하는 것처럼 보인다: happy path만 확인하고 넘어가면 edge case에서 조용히 깨진다.
- 이해하지 못한 code가 쌓인다: 내가 쓴 code가 아니니 디버깅도 어렵고, 기술 부채가 빠르게 불어난다.
- 보안과 정합성이 빠진다: “돌아가네”와 “맞네”는 다른 말이다. 느낌은 앞의 것까지만 알려준다.
vibe coding을 두고 자주 나오는 농담이 “works until it doesn’t”다. 될 때까지는 되다가, 안 되는 순간이 오면 왜 안 되는지 아무도 모른다. 이런 장애는 대개 이 모양이다. 누가 썼는지 모를 code가 있고, 그 code가 무엇을 가정했는지 아는 사람이 없다.
느낌에 기대는 경우와 검증 loop를 붙인 경우를 나란히 놓으면 이렇다.
| 구분 | vibe coding만 | 검증 harness를 붙이면 |
|---|---|---|
| 확인 방식 | 돌아가는 것 같다는 느낌 | 실제 실행 결과 |
| 실패 시점 | works until it doesn’t | 격리된 공간에서 먼저 드러남 |
| 위험 | main이 곧바로 깨질 수 있음 | main은 안전하게 유지됨 |
| 이해도 | 안 쓴 code가 그대로 쌓임 | 실행 log로 확인하면서 이해 |
vibe는 그대로 두고 검증을 붙인다
해법이 vibe coding을 그만두는 건 아니다. 빠르게 만드는 건 그대로 두고, 생성 뒤에 검증 loop를 붙인다. 의도, 생성, 실제 실행, 검증, 통과하면 반영. 요즘 내가 LLM과 함께 code를 만들 때 지키는 순서도 이렇다. 격리된 작업 공간에서 만들고, 직접 돌려서 확인한 다음에만 합친다.
# (1) Vibe: 생성물을 바로 main에 올린다. 돌아가는 "것처럼" 보일 뿐이다
# (2) Harness: 격리 -> 생성 -> 실제 실행으로 검증 -> 통과해야 merge
git worktree add ../wt-feature -b feature/x # 격리된 작업 공간
# (LLM이 wt-feature 안에서 구현)
cd ../wt-feature
pytest -q && pnpm typecheck # 추측이 아니라 실행으로 검증
# 통과하면 merge, 아니면 다시. main은 깨지지 않는다
내가 붙잡는 원칙은 “추측에서 멈추지 말고 실제 결과를 본다” 하나다. vibe coding이 만든 속도 아래에 검증이라는 바닥을 까는 일이다. 격리는 실패해도 괜찮은 공간을 만들고, 실행은 “돌아가는 것 같다”를 “돌아간다”로 바꾼다.
여기서 worktree는 격리를 구현하는 한 가지 방법일 뿐이다. branch든 container든 별도 환경이든, 격리된 곳에서 만들고 실제로 돌려본 뒤에 합친다는 원칙만 지키면 도구는 상황에 맞게 고르면 된다. 반대로 말하면 이 loop도 test가 잡는 범위까지만 지켜준다. pytest가 통과했다는 건 내가 써둔 test를 통과했다는 뜻이지, test가 다루지 않는 edge case까지 맞다는 뜻은 아니다. 그래서 LLM이 code를 쓸 때 test도 같이 쓰게 하되, 그 test가 무엇을 확인하는지는 사람이 한 번은 읽어야 한다고 생각한다.
다음 단계로 넘어가는 이유
vibe coding은 만드는 마찰을 지워서 속도와 접근성을 열었다. 하지만 prompting의 “검증 없음”을 그대로 안고 있어서, 검증 harness 없이 쓰면 “works until it doesn’t”에서 벗어나기 어렵다. 빠르게 만들되 격리하고, 실행하고, 확인한다. vibe를 실제 업무에 쓸 수 있는 수준으로 끌어올리는 건 그 작은 차이다.
지금까지는 그 loop를 사람이 돌렸다. 만들고, 돌려보고, 고치는 일을 내가 했다. 그렇다면 모델이 직접 tool을 쓰고 결과를 보면서 loop를 돈다면 어떻게 될까. 다음 편에서 다룰 agent가 그 이야기다.



