바이브 코딩을 제대로 쓰는 법: Cursor·Copilot·Claude의 컨텍스트 수집과 검증 습관
이 글의 핵심
AI에게 코드를 맡겼는데 엉뚱한 파일을 참고하거나 그럴듯한 틀린 코드를 내놓는 경우가 많습니다. 원인은 대개 도구가 모은 컨텍스트에 있습니다. 편집기 상태·저장소 검색·명시적 앵커로 채워지는 토큰 예산, 디바운스와 후보 재순위 같은 내부 동작을 짚은 뒤, 결과물을 테스트와 리뷰 게이트로 걸러내는 실무 습관을 정리합니다.
들어가며
AI 바이브 코딩(Vibe Coding)은 AI 도구와 자연어로 대화하며 코드를 작성하는 프로그래밍 방식입니다. 2024년부터 빠르게 확산되어 2026년 현재는 많은 개발팀의 일상적인 작업 방식으로 자리 잡았습니다. 다만 이 글에서 강조하고 싶은 것은 속도 그 자체보다 “어떻게 써야 실제로 도움이 되는가”입니다. 같은 도구를 써도 결과물의 품질과 작업 속도는 사용자의 습관에 따라 크게 갈리기 때문입니다.
비유하자면 전통적 코딩이 혼자서 집을 짓는 것이라면, 바이브 코딩은 손이 매우 빠르지만 이 프로젝트의 맥락을 모르는 팀원과 함께 집을 짓는 것에 가깝습니다. “이런 기능이 필요해”라고 말하면 AI가 즉시 무언가를 만들어내지만, 그 결과물이 이 프로젝트의 기존 스타일·제약·에러 처리 정책에 맞는지는 여전히 사람이 확인해야 합니다.
바이브 코딩이란?
전통적 코딩 vs 바이브 코딩
전통적인 흐름에서는 검색과 문서 읽기로 사용법을 익히고, 코드를 직접 작성한 뒤 디버깅과 리팩토링을 거칩니다. 바이브 코딩에서는 “React로 TODO 앱 만들어줘”처럼 요구를 말로 전달하고, AI가 만든 초안에 “다크 모드 추가해줘” 같은 피드백을 반복해 붙여 나갑니다. 예를 들어 다음 반복문은 “배열에서 짝수만 필터링해줘”라는 한 문장으로 요청할 수 있습니다.
for (size_t i = 0; i < arr.size(); i++) {
if (arr[i] % 2 == 0) {
result.push_back(arr[i]);
}
}
다만 AI가 내놓는 것은 “최적의 코드”가 아니라 학습 데이터에서 가장 흔한 형태의 코드입니다. 이 프로젝트가 std::copy_if나 범위 기반 알고리즘을 선호하는지, 결과를 새 벡터로 반환해야 하는지는 프롬프트에 적지 않으면 알 수 없습니다.
바이브 코딩의 장점과 전제
반복적인 보일러플레이트 작성 시간이 줄어들고, 익숙하지 않은 라이브러리의 관용적인 사용법을 예시 코드로 빠르게 접할 수 있습니다. 구현 세부를 AI에 맡기는 만큼 설계와 검토에 시간을 더 쓸 여지도 생깁니다. 하지만 이런 효과는 도구가 보장하는 것이 아니라 검증 습관이 갖춰졌을 때 기대할 수 있는 방향성에 가깝습니다. 검증 없이 생성 속도만 높이면 오히려 버그와 기술 부채가 늘어날 수 있다는 점은 뒤에서 다시 다룹니다.
주요 AI 도구
Cursor
VS Code를 포크해 만든 AI 에디터입니다. 저장소를 인덱싱해 프로젝트 전체를 컨텍스트로 끌어올 수 있고, 여러 파일을 한 번에 고치는 에이전트 모드를 제공합니다. 모델은 Claude, GPT 계열 등 여러 공급자의 모델 중에서 고를 수 있습니다.
기본 단축키는 다음과 같습니다.
Cmd+K (Mac) / Ctrl+K (Windows)
→ "이 함수를 TypeScript로 변환해줘"
Cmd+L (Mac) / Ctrl+L (Windows)
→ 채팅 모드로 대화
@파일명
→ 특정 파일 컨텍스트 포함
예를 들어 “@app.py 이 Flask 앱에 JWT 인증 추가해줘”라고 요청하면, 보통 flask-jwt-extended 설치, JWT 설정 추가, 로그인 엔드포인트 생성, 보호할 라우트에 @jwt_required() 데코레이터 적용까지 한 번에 제안합니다. 제안이 빠른 만큼, 토큰 만료 시간이나 비밀 키를 어디서 읽는지 같은 세부는 직접 확인해야 합니다.
GitHub Copilot
커서 주변 코드와 주석을 보고 다음 줄이나 함수 몸통을 회색 고스트 텍스트로 제안하는 인라인 완성이 출발점입니다. VS Code, JetBrains IDE, Vim/Neovim 등 주요 에디터를 지원하며, 지금은 채팅과 에이전트 모드도 함께 제공합니다.
// TODO 리스트를 로컬스토리지에 저장하는 함수
function saveTodos(todos) {
// Tab 누르면 AI가 자동완성
localStorage.setItem('todos', JSON.stringify(todos));
}
주석으로 의도를 적으면 함수 전체를 제안하기도 합니다.
# 이진 탐색 구현
def binary_search(arr, target):
# Tab → AI가 전체 구현 생성
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
Claude (Anthropic)
Anthropic의 모델로, 200K 토큰 이상의 긴 컨텍스트 창을 지원해 큰 파일이나 여러 문서를 함께 넣고 설계·리뷰·리팩토링을 논의하기에 적합합니다. 채팅 앱 외에 터미널에서 저장소를 직접 읽고 고치는 Claude Code로도 쓸 수 있습니다. 긴 설계 요청은 다음처럼 범위를 나열해 줍니다.
프롬프트:
"마이크로서비스 아키텍처로 전자상거래 시스템 설계해줘.
- 사용자 서비스
- 상품 서비스
- 주문 서비스
- 결제 서비스
각 서비스의 API, 데이터베이스 스키마, Docker Compose 파일 포함"
이런 요청은 한 번에 그럴듯한 설계와 코드를 받을 수 있지만, 서비스가 넷이면 검토할 결정도 그만큼 많습니다. 뒤에서 다루듯 서비스 하나씩 나눠 요청하는 편이 검증하기 쉽습니다.
Windsurf
Codeium이 만든 VS Code 기반 AI 에디터로, 무료 플랜이 있어 Cursor의 대안으로 자주 거론됩니다. 에이전트형 편집 기능(Cascade)을 중심으로 Cursor와 비슷한 작업 방식을 지원합니다.
v0 (Vercel)
UI 컴포넌트 생성에 특화된 도구입니다. “다크 모드 지원하는 프로필 카드 컴포넌트 만들어줘”처럼 요청하면 React와 Tailwind CSS 기반 컴포넌트를 만들고 브라우저에서 바로 미리 보여 줍니다. 결과 코드를 프로젝트로 옮길 때는 기존 디자인 토큰이나 컴포넌트 라이브러리와 맞는지 확인해야 합니다.
LSP와 에디터 통합
현대 IDE의 언어 기능(정의로 이동, 참조 찾기, 이름 바꾸기, 진단 메시지)은 대부분 언어 서버(language server)와 에디터 클라이언트가 Language Server Protocol(LSP)로 통신하여 구현됩니다. LSP는 Microsoft가 주도해 표준화한 JSON-RPC 기반 프로토콜로, 특정 언어용 분석기(typescript-language-server, rust-analyzer, pylsp 등)를 에디터에 묶지 않고도 동일한 UX를 재사용할 수 있게 합니다.
AI 코딩 도구와의 관계는 오해하기 쉽습니다. LSP 자체가 “대형 언어 모델”을 돌리는 것은 아니며, 구문·타입·심볼 테이블 같은 구조화된 언어 정보를 빠르게 제공하는 쪽에 가깝습니다. 반면 채팅형 보조나 고스트 텍스트는 별도의 코드 모델·검색·RAG 파이프라인을 탑니다. 실무에서 체감하는 “똑똑한 자동완성”은 LSP가 준 타입/스코프 정보와 모델이 생성한 토큰 시퀀스가 합쳐진 결과인 경우가 많습니다.
LSP가 주는 것과 주지 않는 것
- 주는 것: 진단(
textDocument/publishDiagnostics), 정의/구현/참조(textDocument/definition등), 문서 심볼(textDocument/documentSymbol), 호버 타입 정보(textDocument/hover), 포맷·리네임 등 편집 작업. - 주지 않는 것: 자연어 수준의 “의도” 추론, 비즈니스 규칙 검증, 레포 전체의 런타임 동작 보장. 이는 테스트·린터·CI·모델 컨텍스트가 담당합니다.
Cursor·VS Code 계열에서는 동일한 버퍼에 대해 LSP 세션이 유지되고, AI 기능은 열려 있는 파일, 커서 위치, 최근 편집을 함께 묶어 프롬프트나 검색 쿼리를 구성합니다. 따라서 LSP가 정상 동작하지 않는 프로젝트(잘못된 tsconfig, 깨진 가상환경, 인덱싱 실패)에서는 AI 제안도 함께 흔들리는 것이 일반적입니다.
LSP 쪽에서 먼저 확인할 것
- 언어 서버가 초록불인지 확인합니다. 문제가 있으면 먼저 LSP/의존성을 고치고, AI에 “이상하다”고만 넘기지 않습니다.
- 워크스페이스 루트와 단일 진실 원본(예: 모노레포의
tsconfig참조)이 맞는지 확인합니다. - 대규모 생성 작업 전에 심볼 탐색으로 기존 API를 파악해, 모델이 중복 타입·이름 충돌을 만들지 않게 합니다.
프로세스 모델과 전송 계층
LSP는 에디터(클라이언트)와 언어 서버(서버)가 분리된 프로세스로 동작하는 것을 전제로 합니다. VS Code·Cursor 계열에서는 보통 확장 호스트(extension host)가 서버 프로세스를 띄우고, 통신은 JSON-RPC 2.0 위에 정의된 메서드 이름(예: textDocument/hover)으로 이루어집니다. 전송은 표준 입력·출력(stdio) 파이프가 흔하며, 일부 구현은 소켓이나 Node IPC를 쓰기도 합니다. 중요한 점은 UI 스레드와 분리되어 무거운 구문 분석이 에디터를 멈추지 않게 한다는 것입니다.
수명 주기: initialize부터 shutdown까지
세션이 시작되면 클라이언트는 initialize 요청으로 클라이언트·서버 역량(capabilities)을 교환합니다. 여기서 워크스페이스 폴더, 문서 동기화 방식, 진단 지원 여부, 코드 액션·리네임 같은 기능이 합의됩니다. 이후 initialized 알림으로 준비 완료를 알리고, 편집기는 textDocument/didOpen, textDocument/didChange로 버퍼 상태를 서버에 반영합니다. 작업을 끝낼 때는 shutdown → exit 순으로 정리하는 것이 권장됩니다. AI 기능은 이 동일한 “열린 문서 집합”을 참조할 수 있지만, LSP 메시지를 그대로 보내는 것과 모델에 텍스트를 넣는 것은 별도 경로인 경우가 많습니다.
문서 동기화: 전량 vs 증분
textDocument/didChange에는 두 가지 스타일이 있습니다. 전량(full) 동기화는 매번 파일 전체를 보내고, 증분(incremental) 동기화는 Range 기반 편집(삽입·삭제)만 보냅니다. 후자는 대용량 파일에서 네트워크·CPU 모두에 유리하며, 서버는 줄·문자 위치(기본은 UTF-16 코드 유닛이며, LSP 3.17부터는 positionEncoding으로 UTF-8·UTF-32를 협상할 수 있음)에 맞춰 내부 버퍼를 갱신해야 합니다. 클라이언트와 서버의 버전이 어긋나면 진단 위치가 틀어지고, AI가 “보는 코드”와 LSP가 “아는 코드”가 달라져 이상한 자동완성이 나옵니다. 저장 전후나 포맷 직후에는 잠깐이라도 이런 드리프트가 생길 수 있으므로, 실무에서는 저장 후 빌드·타입체크 통과를 한 번의 고정점으로 삼는 것이 안전합니다.
능력 협상과 확장 포인트
completionProvider, hoverProvider, definitionProvider 등은 선택 기능입니다. 서버가 특정 메서드를 지원하지 않으면 클라이언트는 해당 UX를 비활성화합니다. 반대로 코드 렌즈·인레이 힌트·시맨틱 토큰처럼 “보조적이지만 강력한” 기능은 버전·플러그인에 따라 달라집니다. AI 코딩 도구는 여기에 더해 프로젝트 인덱스·임베딩 검색·웹 검색을 붙이므로, LSP = 언어의 문법·타입 경계, AI = 의도·보일러플레이트·교차 파일 추론으로 역할을 나누어 이해하면 설계와 디버깅이 쉬워집니다.
아키텍처 관점 정리: 한 화면 안의 두 파이프라인
같은 커서 위치에서 정의로 이동은 LSP가 권위를 가지며, 다음 줄 전체 제안은 코드 모델이 권위를 가집니다. 둘은 경쟁이 아니라 상호 보완 관계입니다. 타입·스코프가 맞으면 모델은 시그니처에 맞는 식별자를 고를 확률이 높아지고, 모델이 틀려도 진단 빨간 줄이 빠르게 잡아냅니다. 따라서 “AI만 믿고 LSP 경고를 무시”하는 것은 가장 비싼 실수가 됩니다.
자동완성·인라인 제안의 모델 구조
GitHub Copilot 스타일의 인라인 제안(ghost text)과 채팅·에이전트형 편집은 같은 브랜드라도 내부 파이프라인이 다를 수 있습니다. 개념적으로는 다음 층으로 나누어 이해하면 운영·튜닝에 유리합니다.
프롬프트 조립기(prompt assembler)
모델 앞단에서 현재 파일 일부, 인접 파일, LSP가 알려 준 시그니처, 프로젝트 메타데이터(패키지 매니저, 프레임워크 버전)를 토큰 예산 안으로 압축합니다. 여기서 잘린 컨텍스트는 곧 환각(hallucination)으로 이어집니다.
코드 특화 모델과 일반 모델
- 인라인 완성: 짧은 지연과 높은 빈도가 요구되어, 소형·코드 특화 모델이나 캐시·스펙큘레이티브 디코딩이 쓰이기도 합니다.
- 멀티파일 편집·리팩터: 더 큰 컨텍스트와 추론이 필요해 고용량 모델이나 도구 호출(파일 읽기/쓰기) 루프가 붙습니다.
검색 증강(RAG)과 인덱스
일부 제품은 임베딩 검색으로 레포 내 유사 코드·문서를 붙이며, 심볼 인덱스로 “이 이름이 어디에 쓰이는지”를 빠르게 찾습니다. 이는 순수 LM만으로는 어려운 대규모 코드베이스 일관성을 보완합니다.
후처리·안전 필터
생성 직후 정적 분석, 포맷터, 비밀 스캔이 걸리는 경우가 있습니다. 사용자에게는 “한 번에 나온 문장”처럼 보여도, 내부적으로는 후보군·재순위화가 있을 수 있습니다.
요약하면, “탭 한 번”의 뒤에는 단일 거대 모델이 아니라 “컨텍스트 선택 → 생성 → (선택적) 검증”의 파이프라인이 있는 경우가 많습니다. 이 관점이 있으면 왜 같은 질문이라도 파일을 열었을 때와 안 열었을 때 답이 다른지를 기술적으로 설명할 수 있습니다.
Fill-in-the-Middle(FIM)과 프리픽스·서픽스
인라인 완성은 문장을 “앞에서부터만” 쓰는 챗과 달리, 커서 앞(프리픽스)과 뒤(서픽스)가 동시에 주어지는 경우가 많습니다. 모델은 중간을 메우는 방식으로 토큰을 배열하며, 이는 기존 코드와의 정렬(닫는 괄호·들여쓰기·주석 블록)을 맞추는 데 유리합니다. 서픽스가 강하게 주어질수록 “끝까지 생성”하다 충돌하는 일이 줄어듭니다. 반대로 서픽스 정보가 약하면 유령 괄호·중복 블록이 생기기 쉽습니다.
토큰화·어휘와 “코드 특화” 모델
소스 코드는 자연어와 달리 긴 식별자·경로·베이스64·정규식이 섞여 있습니다. 바이트 페어 인코딩(BPE) 계열 토크나이저는 이런 문자열을 쪼개는 규칙에 따라 토큰 수가 달라집니다. 따라서 같은 줄이라도 토큰 경계가 어디냐에 따라 컨텍스트 창 소모가 달라집니다. 코드 특화 모델은 언어 키워드·관용 구조에 로짓(logit) 분포가 이미 편향되어 있어 짧은 지연에서 유리합니다. 대신 드문 도메인 용어(예: 사내 프레임워크)에는 일반 모델보다 불리할 수 있어, 그때는 RAG·심볼 테이블이 더 큰 비중을 차지합니다.
지연(latency)·디바운스·취소 토큰
타이핑 중 매 키마다 요청을 보내면 비용과 혼잡이 폭증합니다. 실제 구현은 디바운스(수십 ms 단위로 묶기), 이전 요청 취소(새 입력 시 이전 스트림 중단), 최소 프리픽스 길이 같은 품질·비용 제어를 둡니다. 사용자가 체감하는 “반응성”은 첫 토큰까지의 시간(TTFT)과 초당 토큰이 함께 결정하며, 긴 제안은 스트리밍으로 보여 주다가도 뒤늦게 잘리는 경우가 있습니다. 따라서 “탭 직전에만 보이던 글자가 사라짐”은 버그라기보다 취소·재순위의 흔한 부작용입니다.
스펙큘레이티브·캐시·후보 재순위
일부 시스템은 여러 후보를 내부적으로 생성하고 정적 규칙(타입 호환, 린트 통과, 포맷 일치)으로 재순위화합니다. 또 최근 반복 패턴·같은 레포의 유사 스니펫을 캐시해 비용을 줄이기도 합니다. 공개된 세부 구현은 제품마다 다르지만, 운영 관점에서 기억할 것은 “가장 그럴듯한 한 줄”이 항상 최선의 설계는 아니다는 점입니다. API 계약이나 보안 제약은 재순위 단계에서도 완전히 포착되지 않을 수 있습니다.
LSP completion과의 관계
textDocument/completion이 주는 목록(메서드 이름·스니펫)은 결정론적에 가깝고, 고스트 텍스트는 확률적입니다. 둘 다 “다음에 뭘 쓸지”를 돕지만 신뢰도의 근거가 다릅니다. 실무에서는 시그니처가 맞는지는 LSP·타입체크에, 구현 디테일은 모델에 묻는 식으로 질문을 분리하면 혼선이 줄어듭니다.
컨텍스트 수집 파이프라인
AI 에디터가 “프로젝트를 안다”고 느껴질 때, 그 배경에는 보통 다음 컨텍스트 소스가 조합되어 있습니다.
편집기 상태
- 현재 버퍼의 커서 전후, 선택 영역, 최근 undo 히스토리에 가까운 변경.
- 열린 탭과 최근 본 파일(MRU). 사람이 주의를 두는 곳과 비슷하게 가중치가 붙습니다.
저장소·검색
- 워크스페이스 전체 스캔 또는 임베딩 인덱스로 관련 파일을 찾아 붙입니다.
.gitdiff나 브랜치 대비 변경을 넣어 “지금 작업 중인 맥락”을 강화하기도 합니다.
명시적 앵커
Cursor의 @파일, @폴더, @문서 같은 명시적 포함은 모델이 추측하지 않고도 근거를 고정할 수 있어 품질 변동을 줄입니다. 팀 단위로는 AGENTS.md, CLAUDE.md, 코딩 가이드를 레포에 두어 반복 규칙을 텍스트로 고정하는 패턴이 흔합니다.
LSP·빌드 그래프에서 오는 힌트
import 그래프, 타입 의존성, 빌드 타겟 정보는 “이 변경이 어디까지 파급되는지”를 좁히는 데 도움이 됩니다. 모델이 전부를 읽지 못할 때 경계가 잡힌 모듈 단위로 작업을 쪼개면 컨텍스트 누락을 줄일 수 있습니다.
아래는 개념적 흐름을 한눈에 정리한 다이어그램입니다.
flowchart LR
subgraph sources["컨텍스트 소스"]
A[열린 파일·커서]
B[Git diff·브랜치]
C[LSP 심볼·진단]
D[임베딩 검색·인덱스]
E[사용자 @멘션·규칙 파일]
end
subgraph asm["조립"]
P[토큰 예산·압축]
end
subgraph model["모델"]
M[코드 LM / 도구 루프]
end
sources --> P --> M
컨텍스트 예산(token budget) 할당 전략
모델 입력은 유한한 토큰 예산을 가집니다. 수집기는 보통 우선순위 큐에 가깝게 동작하며, (1) 현재 파일의 커서 주변, (2) 최근 수정(diff에 등장한 영역), (3) 열린 탭·고정(pin)된 파일, (4) 심볼 이름으로 검색된 이웃 파일, (5) 임베딩 유사도 상위 청크 순으로 채웁니다. 여기서 한 가지라도 빠지면 “그 파일을 열었을 때만 맞는 답”이 나옵니다. 팀에서는 작업 단위를 모듈 경계에 맞추고, 필요한 파일을 명시적으로 @로 고정하는 습관이 품질을 안정화합니다.
검색 혼합: 키워드(BM25) + 벡터 + 그래프
대규모 레포에서는 문자열 grep만으로는 부족하고, 의미적 유사도(벡터)만으로는 정확한 식별자 매칭이 흔들립니다. 따라서 실제 시스템은 하이브리드 검색을 쓰는 경우가 많습니다. 파일 경로·심볼 이름은 키워드 검색이 강하고, “이 에러와 비슷한 처리”는 벡터 검색이 강합니다. 한편 import·호출 그래프를 따라 1~2홉(hop)만 넓혀도 연관 모듈을 빠르게 붙일 수 있습니다. 이 그래프는 LSP가 제공하는 reference 정보와 겹치기도 하며, 정적 분석이 없는 동적 언어에서는 휴리스틱에 더 의존하기도 합니다.
청킹(chunking)과 경계
파일 전체를 넣지 못할 때는 청크로 자릅니다. 청크 경계가 함수 중간이면 모델은 시그니처를 모른 채 몸통만 보게 됩니다. 좋은 청킹은 구문 트리·AST 기준으로 함수·타입·블록 단위를 존중합니다. 사용자 측 완화책은 관련 심볼 위에 짧은 주석 헤더를 두어, 잘린 조각에도 의도가 남게 하는 것입니다.
부정 컨텍스트: 무엇을 넣지 않을 것인가
node_modules, 빌드 산출물, 거대한 lockfile, 생성된 protobuf 같은 것은 신호 대 잡음 비율을 망칩니다. .cursorignore, .gitignore 정렬, 생성 코드 분리 디렉터리는 “AI가 읽을 범위”를 통제하는 실질적 스코프 장치입니다. 또 비밀·토큰은 수집 파이프라인에 들어가면 안 되는 최우선 부정 컨텍스트입니다. 프롬프트에 붙기 전에 시크릿 스캔이 걸리는 환경이라도, 근본적으로는 저장소와 클라이언트 설정에서 차단하는 것이 맞습니다.
멀티 루트·모노레포에서의 “프로젝트 선택”
같은 워크스페이스에 여러 패키지가 있으면, 어느 tsconfig/pyright/go.work가 이 커서에 적용되는지가 LSP와 수집기 모두에 영향을 줍니다. 잘못된 루트 인식은 “타입은 맞는데 import 경로가 이상하다” 같은 반쪽짜리 컨텍스트를 만듭니다. 모노레포에서는 패키지별 README와 아키텍처 다이어그램을 짧게라도 두면, 사람과 기계 모두 경계를 덜 틀립니다.
운영 팁: 컨텍스트 품질을 올리는 습관
- 작업 브랜치를 최신화해 diff와 인덱스가 엇나가지 않게 합니다.
- 대화 세션을 주제별로 나누고, 길어진 잡담 컨텍스트는 새 채팅으로 리셋합니다.
- 재현 가능한 최소 예제를 붙이면, 모델이 추측으로 채우는 영역이 줄어듭니다.
증분 분석과 제안 품질
언어 서버와 컴파일러는 매 키 입력마다 전체를 처음부터 다시 돌리지 않고, 변경된 부분과 그에 의존하는 단위만 다시 계산하는 증분(incremental) 전략을 씁니다. TypeScript의 tsserver, Rust의 rust-analyzer(내부적으로 증분 재분석), C++의 일부 인크리멘털 빌드는 다루는 문제는 다르지만 철학은 비슷합니다.
왜 AI 사용자에게도 중요한가
- 제안 지연(latency): 거대한 단일 파일을 무리하게 유지하면 LSP와 포맷터가 버거워지고, 그만큼 피드백 루프가 느려집니다.
- 진단 일관성: 부분적으로만 타입이 풀린 상태에서 모델이 코드를 끼워 넣으면, 겉보기에만 맞는 잘못된 시그니처가 나올 수 있습니다.
- 대규모 리팩터: “전부 다시 분석”에 가까운 작업은 IDE와 CI 모두 비용이 크므로, 모듈 경계·공개 API를 유지한 채 작은 PR로 나누는 것이 기계와 사람 모두에게 유리합니다.
실무 권장
- 타입·린트가 깨지지 않은 상태를 자주 유지해, 언어 서버가 최신 트리를 들고 있게 합니다.
- 장시간 생성보다 스캐폴딩 → 빌드 → 오류 고치기 루프를 짧게 반복하면, 증분 분석이 제공하는 정확한 진단을 더 자주 활용할 수 있습니다.
- 모노레포에서는 프로젝트 참조와 캐시(예: 빌드 시스템의 incremental 플래그)를 점검해, “저장 한 번에 수십 초” 같은 상황을 줄입니다.
무효화(invalidation)와 의존 그래프
증분 분석의 핵심은 “무엇이 바뀌었을 때 무엇을 다시 계산할까”입니다. 컴파일러·언어 서버는 보통 파일 단위를 넘어 심볼·모듈 간 의존성 그래프를 유지합니다. 한 함수의 시그니처가 바뀌면, 그 심볼을 참조하는 파일의 타입 정보가 연쇄적으로 갱신됩니다. TypeScript는 프로젝트 그래프와 소스 파일 간 영향 범위를 추적하며, Rust의 rust-analyzer는 내부적으로 증분 DB(Salsa 스타일의 쿼리 시스템에 가까운 구조)로 파싱·매크로·이름 해석 결과를 메모이즈합니다. 사용자에게는 “조금 고쳤는데 진단이 따라온다”로 보이지만, 뒤에서는 대규모 그래프 갱신이 일어납니다.
더티(dirty) 상태와 편집 중인 불일치
에디터 버퍼는 디스크의 파일과 항상 같지 않습니다. LSP는 메모리상의 텍스트를 기준으로 분석하는데, 저장되지 않은 상태에서 외부 도구(터미널의 빌드)는 옛 파일을 봅니다. AI가 제안한 코드가 포맷터·import 정리와 경합하면 순간적으로 타입이 깨져 보일 수 있습니다. 실무에서는 저장 → 진단 안정화 → 생성 순서를 짧게 반복하거나, 동일한 단일 소스를 기준으로 도구를 맞춥니다.
구문 트리·바인딩·타입 검사의 단계화
대부분의 언어 서버는 (1) 토큰화/파싱, (2) 이름 해석(스코프·import), (3) 타입 검사, (4) 상위 레벨 분석(리팩터 힌트 등)처럼 단계가 쌓인 파이프라인입니다. 앞 단계가 흔들리면 뒤 단계 전체가 의미를 잃습니다. AI는 종종 타입 검사 전에도 그럴듯한 코드를 만들 수 있으므로, “진단이 없다”와 “맞다”를 같은 것으로 보면 안 됩니다. 특히 부분 파싱 상태(중간에 괄호가 닫히지 않음)에서는 LSP 기능이 제한되고, 모델은 완성된 형태를 상상해 끼워 넣기 쉽습니다.
CI와의 괴리: 로컬 증분 vs 클린 빌드
로컬 IDE는 증분을 전제로 하지만, CI는 클린 환경에서 전체 검증을 돌리는 경우가 많습니다. 따라서 “내 PC에선 되는데 CI에서만 실패”하는 일이 흔합니다. AI가 환경 변수·시스템 의존성을 빠뜨리면 이 괴리가 커집니다. 컨테이너화된 재현 스크립트와 동일한 린트 규칙을 로컬에 맞추면, 증분 분석이 주는 빠른 피드백과 CI의 엄격함을 같은 축에 놓을 수 있습니다.
대용량 리팩터링 시 “경계 유지”가 중요한 이유
한 번에 수백 파일을 바꾸면 언어 서버는 대규모 무효화에 들어가고 IDE가 버벅일 수 있습니다. 게다가 모델은 토큰 예산 때문에 전체를 한 번에 “정확히” 볼 수 없습니다. 공개 API 표면을 유지한 채 패키지 단위로 쪼개면 증분 분석·리뷰·롤백이 모두 쉬워집니다. 이는 프로덕션 AI 코딩 패턴의 작은 변경 단위와 직결됩니다.
프롬프트 엔지니어링
효과적인 프롬프트 작성법
나쁜 프롬프트:
"로그인 기능 만들어줘"
좋은 프롬프트:
"Next.js 14 App Router로 로그인 기능 구현해줘.
요구사항:
- 이메일/비밀번호 로그인
- JWT 토큰 기반 인증
- 로그인 상태 유지 (httpOnly·Secure 쿠키에 토큰 저장, localStorage 사용 금지)
- 로그인 실패 시 에러 메시지
- Zod로 입력 검증
- Tailwind CSS로 스타일링
파일 구조:
- app/login/page.tsx (로그인 페이지)
- app/api/auth/login/route.ts (API)
- lib/auth.ts (JWT 유틸리티)
"
프롬프트 패턴
- 역할 지정 (Role)
"당신은 시니어 백엔드 개발자입니다.
Node.js와 PostgreSQL로 RESTful API를 설계해주세요."
- 컨텍스트 제공 (Context)
"현재 프로젝트는 Express.js 기반이며,
Prisma ORM을 사용합니다.
기존 User 모델에 Post 모델을 추가하고 1:N 관계를 설정해주세요."
- 예시 제공 (Example)
"다음과 같은 형식으로 API 응답을 만들어주세요:
{
"success": true,
"data": { ... },
"error": null
}
- 제약사항 명시 (Constraint)
"TypeScript를 사용하며,
외부 라이브러리는 최소화하며,
모든 함수에 JSDoc 주석을 추가해주세요."
멀티턴 대화 전략
1단계: 큰 그림
"블로그 시스템 아키텍처 설계해줘"
2단계: 구체화
"User 모델의 데이터베이스 스키마를 Prisma로 작성해줘"
3단계: 구현
"회원가입 API 엔드포인트 구현해줘"
4단계: 개선
"이메일 중복 체크 추가하며, 비밀번호는 bcrypt로 해싱해줘"
실전 워크플로우
워크플로우 1: 새 프로젝트 시작
스캐폴딩은 AI가 가장 잘하는 일입니다. 다만 한 번에 전부 요청하지 않고 단계마다 결과를 실행해 보며 진행합니다.
Cursor에서:
1. "Next.js + TypeScript + Tailwind + Prisma로
블로그 프로젝트 초기 설정해줘"
→ 생성된 구조를 보고 dev 서버가 뜨는지 확인
2. "Prisma 스키마에 User, Post, Comment 모델 추가해줘"
→ 관계와 인덱스를 검토한 뒤 마이그레이션 실행
3. "홈페이지에 최근 포스트 목록 보여주는 컴포넌트 만들어줘"
→ 빈 목록·로딩 상태를 직접 확인
4. "다크 모드 지원 추가해줘"
워크플로우 2: 버그 수정
에러 메시지만 붙여넣고 “이 에러 해결해줘”라고 하면, AI는 증상을 지우는 방향으로 고치는 경우가 많습니다. 스택 트레이스 전체와 재현 절차, 관련 파일을 함께 주고 먼저 원인 설명을 요청하는 편이 안전합니다.
Cursor에서:
1. 스택 트레이스와 재현 절차를 채팅(Cmd+L)에 붙여넣기
2. "@파일명 이 에러의 원인을 먼저 설명해줘. 아직 코드는 고치지 마"
3. 원인 설명이 납득되면 수정 요청
4. 재현 테스트를 추가해 다시 실행
워크플로우 3: 리팩토링
요구사항을 한 프롬프트에 몰아넣으면 뒤쪽 항목이 누락되기 쉽습니다(뒤의 “큰 작업을 작은 단위로 쪼갭니다” 절 참고). 항목 하나씩 적용하고 diff를 확인합니다.
Cursor에서:
1. "@app.py 큰 함수를 작은 단위로 분리해줘. 동작은 바꾸지 마"
→ 테스트 실행, diff 확인
2. "타입 힌트와 Docstring 추가해줘"
3. "에러 핸들링을 프로젝트의 기존 예외 클래스에 맞춰 정리해줘"
4. 성능 개선은 프로파일링 결과를 붙여 별도로 요청
바이브 코딩을 잘하는 실전 방법
지금까지는 도구별 기능과 에디터 내부 동작을 살펴봤습니다. 그런데 실제로 팀에서 AI 코딩 도구를 도입해 보면, 같은 Cursor·Copilot을 쓰고도 결과물의 품질이 사람마다 크게 갈리는 것을 자주 보게 됩니다. 도구의 성능 차이보다 사용하는 사람의 습관 차이가 더 크게 작용하는 경우가 많습니다. 이 절에서는 그 습관의 차이를 구체적으로 정리합니다.
프롬프트를 “만들어줘”로 끝내지 않습니다
“로그인 기능 만들어줘”처럼 목적만 던지는 프롬프트는 AI가 가장 흔한 패턴으로 수렴하게 만듭니다. 모델은 학습 데이터에서 가장 자주 등장한 형태의 로그인 구현을 재현할 뿐, 이 프로젝트가 세션 기반 인증을 쓰는지 JWT를 쓰는지, 에러를 예외로 던지는지 결과 객체로 반환하는지는 알지 못합니다. 그 간극은 결국 사람이 프롬프트에 채워 넣어야 하는 정보입니다.
따라서 효과적인 프롬프트는 기존 코드 스타일(들여쓰기, 네이밍 컨벤션), 성능 요구사항(동기/비동기 처리 방식, 예상 트래픽 규모), 에러 핸들링 정책(예외를 던질지 Result 타입으로 감쌀지) 같은 제약조건을 함께 명시합니다. 이 정보가 없으면 AI는 “틀린 답”이 아니라 “이 프로젝트에는 맞지 않는 답”을 내놓게 되는데, 문제는 겉보기에는 둘을 구분하기 어렵다는 점입니다.
AI가 만든 코드를 그대로 커밋하지 않습니다
AI가 생성한 코드는 문법적으로 완벽하고 그럴듯해 보이지만, 런타임에만 드러나는 오류를 포함하는 경우가 드물지 않습니다. 대표적인 원인은 존재하지 않는 라이브러리 메서드를 마치 실제 API인 것처럼 생성하는 환각(hallucination)입니다. 모델은 비슷한 라이브러리들에서 자주 보이는 메서드 이름의 패턴을 학습했을 뿐, 지금 프로젝트에 설치된 라이브러리의 정확한 버전과 실제 공개 API를 알지 못하기 때문에 이런 일이 생깁니다.
실제로 특정 라이브러리에 있을 법한 이름의 헬퍼 함수를 AI가 자신 있게 제안해서 그대로 가져다 쓴 적이 있습니다. import 문도, 호출부도 문법적으로는 전혀 문제가 없어 보였고 코드 자체도 깔끔했습니다. 그런데 빌드를 돌려보니 “그런 이름의 함수는 없다”는 에러가 났습니다. 실제 라이브러리 문서를 찾아보니 비슷한 이름의 함수가 다른 시그니처로 존재했을 뿐, AI가 제안한 형태의 함수는 애초에 존재하지 않았습니다. 이후로는 AI가 제안한 외부 API 호출은 커밋 전에 반드시 실제로 실행해 보거나, 최소한 해당 라이브러리의 공식 문서에서 시그니처를 대조하는 절차를 거치게 되었습니다.
큰 작업을 한 번에 시키지 않고 작은 단위로 쪼갭니다
여러 파일에 걸친 대규모 리팩토링을 한 번의 프롬프트로 요청하면, 대화가 길어질수록 모델이 앞부분에서 명시한 요구사항을 놓치거나 뒤로 갈수록 일관성이 흐트러지는 현상이 나타납니다. 이는 모델이 게을러서가 아니라, 컨텍스트 예산 안에서 앞선 지시와 지금 생성 중인 코드 사이의 연결을 계속 유지해야 하는데, 대화가 길어질수록 그 연결이 약해지기 때문입니다. 컨텍스트 수집 파이프라인 절에서 다룬 토큰 예산 문제와 같은 원인입니다.
한 번은 대규모 모듈 하나를 통째로 리팩토링해 달라고 요청 목록 다섯 개를 한꺼번에 넣은 적이 있습니다. 결과물을 받아 보니 앞쪽 두세 개 요구사항(함수 분리, 타입 힌트 추가)은 반영되어 있었지만, 뒤쪽에 적었던 에러 핸들링 개선과 성능 최적화는 슬쩍 빠져 있었습니다. 겉으로는 리팩토링이 끝난 것처럼 보였기 때문에 한참 뒤에야 누락을 발견했습니다. 그 뒤로는 다섯 개 요구사항을 한 번에 몰아서 요청하지 않고, 하나씩 적용한 뒤 diff를 확인하고 다음 단계로 넘어가는 방식으로 바꾸었습니다. 단계마다 확인하는 만큼 대화는 길어지지만, 결과물이 누락 없이 반영됐는지 매번 눈으로 확인할 수 있다는 점에서 이쪽이 더 안전합니다.
”왜 이렇게 짰는지” 설명을 요구하는 습관
AI에게 코드만 받고 넘어가지 않고 “왜 이 방식을 선택했는지”, “다른 대안은 없었는지”를 되물으면 두 가지 효과가 있습니다. 첫째, 설명을 생성하는 과정에서 AI 스스로 앞뒤가 맞지 않는 부분을 드러내는 경우가 있어, 그 자체로 버그 발견 수단이 됩니다. 둘째, 사용자 입장에서는 그 설명을 통해 해당 언어나 프레임워크의 관용적인 패턴을 배우게 되므로, 단순히 코드를 복사하는 것보다 학습 효과가 큽니다. 이 습관은 시간이 조금 더 걸리지만, 장기적으로는 프롬프트를 더 정확하게 쓰는 능력으로 이어집니다.
보안·동시성 코드에서는 AI 제안을 곧이곧대로 믿지 않습니다
인증·인가 로직, 암호화, 결제 처리처럼 보안에 민감한 코드나, 락과 원자적 연산이 얽힌 동시성 코드는 AI가 특히 취약한 영역입니다. 이런 코드는 겉보기에 동작하는 것과 실제로 안전한 것 사이의 간극이 크고, 그 간극은 일반적인 테스트로는 잘 드러나지 않기 때문입니다. 예를 들어 경쟁 조건(race condition)은 특정 타이밍에서만 발생하므로, AI가 생성한 락 처리 코드가 로컬 테스트에서 문제없이 통과했다고 해서 프로덕션 트래픽 아래에서도 안전하다는 보장은 없습니다.
이런 영역에서는 AI를 초안 작성이나 보일러플레이트·테스트 케이스 생성에는 활용하되, 실제 위협 모델을 따지고 예외 흐름을 검토하는 마지막 판단은 사람이 맡는 역할 분담이 안전합니다. 뒤에서 다룰 프로덕션 AI 코딩 패턴의 휴먼 게이트 개념과 같은 맥락입니다.
검증 루프를 그림으로 정리하면
아래 다이어그램은 위에서 설명한 습관을 하나의 반복 루프로 정리한 것입니다. 핵심은 “생성”과 “커밋” 사이에 반드시 “실행·검증” 단계가 끼어 있어야 한다는 점입니다.
flowchart LR
A["제약조건 포함한<br/>프롬프트 작성"] --> B[AI가 코드 생성]
B --> C[직접 실행·테스트]
C -->|통과| D[작은 단위로 커밋]
C -->|실패·의심스러움| E["왜 이렇게 짰는지<br/>설명 요구"]
E --> F["코드 수정 또는<br/>대안 재요청"]
F --> C
D --> G{다음 작업 단위?}
G -->|있음| A
G -->|없음| H[완료]
이 루프에서 “직접 실행·테스트” 단계를 생략하고 곧바로 커밋으로 넘어가는 경우가 결과물 품질을 가장 크게 떨어뜨리는 지점입니다. AI가 얼마나 발전하든, 이 검증 단계를 사람이 대신할 수 있는 도구는 아직까지는 등장하지 않았습니다.
생산성 비교
도구 유형별로 달라지는 것
통제된 실험으로 측정한 수치를 제시할 수는 없지만, 도구 유형별로 무엇이 빨라지고 어디서 위험이 생기는지는 구조적으로 설명할 수 있습니다. 인라인 완성(Copilot류)은 반복 타이핑을 줄여 주지만, 제안을 그대로 수용하면 버그도 그대로 들어옵니다. 에이전트형 편집(Cursor류)은 스캐폴딩처럼 여러 파일을 한꺼번에 만드는 작업을 크게 줄여 주는 대신, 한 번에 바뀌는 코드가 많아 검증을 생략했을 때의 피해도 커집니다.
결국 속도가 빨라지는 지점과 품질이 결정되는 지점은 다릅니다. 스캐폴딩 속도가 빨라져도 결과를 검증하지 않고 그대로 커밋하면 버그가 줄기는커녕 늘어날 수 있으며, 앞의 “바이브 코딩을 잘하는 실전 방법” 절에서 다룬 검증 루프가 이 차이를 만듭니다.
도구를 쓰는 습관
AI를 페어 프로그래머로 활용
“전체 프로젝트 만들어줘”라고 요청한 뒤 결과를 그대로 붙여 넣으면, 나중에 그 코드를 고칠 사람이 아무도 구조를 모르는 상태가 됩니다. “User 모델 설계해줘”처럼 작은 단위로 요청하고, 결과를 검토해 피드백을 준 뒤 “비밀번호 해싱 추가해줘”로 넘어가는 식으로 반복하면 각 단계의 결정을 사람이 이해한 채로 쌓을 수 있습니다.
컨텍스트를 명시적으로 지정
Cursor에서는 @ 멘션으로 모델이 참고할 근거를 고정할 수 있습니다.
@파일명 - 특정 파일 포함
@폴더명 - 폴더 포함
@docs - 등록한 라이브러리 문서 포함
@web - 웹 검색 결과 포함
예시:
"@app.py @models.py 이 두 파일을 연동해서 사용자 인증 기능 추가해줘"
리뷰와 학습에 활용
생성뿐 아니라 리뷰어로도 쓸 수 있습니다. “@app.py 이 코드를 보안 취약점, 성능 이슈, 코드 스멜 관점에서 리뷰해줘”처럼 관점을 지정하면 막연한 칭찬 대신 구체적인 지적을 받을 가능성이 높아집니다. 다만 AI 리뷰가 “문제없음”이라고 답한 것이 문제가 없다는 증명은 아니므로, 사람의 리뷰를 대체하는 용도로 쓰지는 않습니다. 모르는 패턴을 만났을 때 “왜 이 방법이 더 나은지”, “다른 접근은 없는지”를 묻는 것도 같은 맥락의 활용입니다.
프로덕션 AI 코딩 패턴
로컬에서의 “빠른 프로토타입”과 운영 환경의 “변경 통제”는 요구사항이 다릅니다. 아래는 팀·서비스 레벨에서 반복적으로 검증되는 패턴입니다.
변경 단위와 게이트
- 작은 배포 단위: AI로 한 번에 많은 파일을 바꾸더라도 논리적 변경은 PR 단위로 쪼갭니다. 리뷰어가 diff를 이해할 수 있는 크기가 상한입니다.
- 기계적 게이트 유지: 린트·타입체크·단위 테스트·스모크 테스트는 AI 도입 이전과 동일하게 통과해야 합니다. 모델 출력은 제안이지 합격 증명이 아닙니다.
- 플래그·점진 롤아웃: 기능 플래그, 카나리, 롤백 계획에는 사람이 작성한 코드와 동일한 엄격도를 적용합니다.
보안·컴플라이언스
- 비밀·토큰·PII는 프롬프트·로그·이슈에 넣지 않습니다.
.env, KMS, 시크릿 매니저가 정답입니다. - 의존성·라이선스: AI가 추가한 패키지는 승인 목록과 스캔을 거칩니다. “편해서” 붙인 의존성이 공급망 리스크가 될 수 있습니다.
- 감사 가능성: “누가·언제·무엇을” 바꿨는지는 Git이 권위를 가집니다. 에이전트가 만든 커밋도 메시지와 티켓 링크로 추적 가능해야 합니다.
관측성·품질
- 새 코드에 로그·메트릭·트레이스를 빠뜨리지 않습니다. AI는 비즈니스 SLO를 모릅니다.
- 회귀 방지: 버그를 수정할 때 재현 테스트를 먼저 두고 생성을 맡기면, “고쳐진 척”하는 출력을 걸러내기 쉽습니다.
- 성능: N+1 쿼리, 불필요한 동기 I/O 같은 패턴은 프로파일링·부하 테스트로 검증합니다.
팀 운영
- 코딩 가이드·금지 패턴을 문서화하고, 에디터 규칙 파일(예: Cursor의
.cursor/rules/)과 함께 리뷰 체크리스트에 넣습니다. - 온콜·장애 대응 시나리오에서는 “AI가 만들었는지”가 아니라 시스템 행동을 기준으로 롤백·완화 조치를 정합니다.
계약 우선 개발: 스키마·OpenAPI·프로토콜 버퍼
서비스 경계가 있는 코드에서는 계약이 진실입니다. OpenAPI, GraphQL 스키마, Protobuf, 이벤트 스키마를 먼저 고정하고 AI에는 “이 계약을 만족하는 구현”만 요청하면 환각 API가 줄어듭니다. 계약 변경은 버전 정책(호환성 깨짐 규칙)과 함께 문서화하고, CI에서 계약 테스트(스키마 린트, 브레이킹 체인지 탐지)를 돌립니다.
테스트·스펙 주도 AI 루프
실패하는 테스트를 먼저 두고 AI에게 구현을 맡기면, “그럴듯하지만 요구를 놓친 코드”를 걸러내기 쉽습니다. 특히 엣지 케이스(빈 입력, 타임아웃, 동시성)는 사람이 스펙을 명문화하는 편이 안전합니다. E2E는 비용이 크므로 핵심 사용자 여정만 남기고, 나머지는 단위·통합 테스트로 층을 나눕니다.
관측성 내장: 로그·메트릭·트레이스를 “나중”이 아니라 “처음”
AI가 생성한 코드는 로그 상관 ID, 에러 래핑, 지표 태그를 빠뜨리기 쉽습니다. PR 템플릿이나 리뷰 체크리스트에 “관측성 증거”를 넣으세요. 새 엔드포인트라면 RED 메트릭, 배치 작업이라면 슬로우 쿼리 알림, 소비자라면 데드 레터 큐 등입니다. 운영 가능성은 기능과 동등한 요구사항으로 취급합니다.
보안·권한 경로의 휴먼 게이트
인증·인가, 결제, 개인정보 마스킹, 암호화 키 회전은 자동 승인 금지 영역으로 두는 팀이 많습니다. AI는 보일러플레이트와 테스트 케이스까지, 사람은 위협 모델과 예외 플로우를 맡는 4-eyes 패턴이 안정적입니다. 또 비밀은 생성 코드에 박지 않고 KMS·시크릿 스토어에서 주입합니다.
플래그·카나리·즉시 롤백
기능 플래그로 AI가 작성한 경로를 소수 트래픽에만 노출하고, 메트릭이 기준을 넘으면 즉시 비활성화합니다. 롤백은 배포 파이프라인과 데이터 마이그레이션의 역방향까지 포함해 연습합니다. “코드만 되돌리면 된다”는 가정은 스키마 변경이 있을 때 깨집니다.
공급망·의존성 거버넌스
AI가 package.json에 패키지 한 줄을 추가하는 것은 순간이지만, 그 영향은 년 단위로 갑니다. 승인된 레지스트리, SBOM, 취약점 스캔, 라이선스 호환을 CI 게이트에 넣습니다. 모델이 제안한 특정 버전은 보안 공지·지원 기간과 함께 검토합니다.
지식 자산화: AGENTS.md, 런북, 사고 포스트모템
팀만의 금지 패턴·필수 패턴을 레포에 텍스트로 고정하면, LSP·컨텍스트 수집기가 읽는 것과 같은 근거가 생깁니다. 장애 대응 런북을 프롬프트에 붙이면 AI는 절차적 체크리스트를 잘 따릅니다. 사후에는 포스트모템으로 프롬프트·가드레일을 갱신해 다음 사고 비용을 줄입니다.
에이전트 출력의 품질 담보: 스냅샷·골든 파일·회귀 스위트
UI나 직렬화 포맷처럼 출력 형태가 중요하다면 스냅샷 테스트나 골든 파일로 고정합니다. AI가 포맷을 살짝 바꿔 클라이언트 파싱을 깨는 사고를 막을 수 있습니다. 대규모 생성 후에는 의미 보존 리팩터와 동작 동등성을 분리해 검증합니다.
요약하면, 프로덕션에서는 AI를 가속 레일로 두되 품질·보안·운영의 책임 모델은 기존 엔지니어링 원칙을 그대로 적용하는 것이 안전합니다. 계약·테스트·관측성·플래그·공급망을 “부가 옵션”이 아니라 출력의 일부로 정의할 때, AI 코딩은 지속 가능한 속도를 냅니다.
트러블슈팅
AI가 작동하지 않는 코드를 생성할 때
에러 메시지를 붙여 “이 에러 해결해줘”라고 요청하는 것이 가장 흔한 대응이지만, 수정 코드만 받으면 같은 실수가 반복됩니다. “이 코드가 왜 작동하지 않는지 먼저 설명해줘”라고 원인을 묻고, 설명이 실제 에러와 맞는지 확인한 뒤 수정을 요청하는 편이 낫습니다.
AI가 프로젝트 구조를 이해하지 못할 때
@폴더명으로 관련 디렉터리를 명시하고, README에 디렉터리 구조와 주요 모듈의 역할을 짧게 적어 두면 도움이 됩니다. 큰 작업 전에는 “코드를 고치기 전에 프로젝트 구조를 먼저 요약해줘”라고 요청해, AI가 이해한 구조가 실제와 맞는지 확인합니다. 어떤 정보가 에디터·검색·LSP를 거쳐 모델 쪽으로 흘러가는지는 컨텍스트 수집 파이프라인 절에서 정리했습니다.
일관성 없는 코드
문제:
AI가 매번 다른 스타일로 코드 생성
해결:
프로젝트 규칙 파일 생성
(Cursor는 .cursor/rules/ 디렉터리, 예전 방식은 루트의 .cursorrules):
코딩 규칙:
- TypeScript 사용
- 함수형 프로그래밍 스타일
- ESLint + Prettier 준수
- 모든 함수에 JSDoc 추가
- 에러는 try-catch로 처리
규칙 파일은 매 요청의 컨텍스트에 함께 들어가므로 짧고 구체적으로 유지하는 편이 좋습니다. 규칙이 지켜졌는지는 린터와 포매터로 다시 확인합니다.
보안에 취약한 코드
“이 코드의 보안 취약점을 OWASP Top 10 기준으로 검토해줘”처럼 리뷰를 따로 요청하면 SQL 인젝션이나 입력 검증 누락 같은 흔한 문제를 짚어 주는 경우가 많습니다. 하지만 같은 모델이 만든 코드를 같은 모델이 검토하는 것이므로 놓치는 부분도 비슷할 수 있습니다. SAST 도구와 의존성 스캐너를 CI에 두고, 인증·결제 경로는 사람이 검토합니다.
마무리
AI 코딩 도구가 내놓는 결과의 품질은 도구가 모은 컨텍스트와 사용자가 거치는 검증 단계에 크게 좌우됩니다. LSP가 주는 구조화된 정보와 모델이 채우는 확률적 제안의 역할을 구분하고, @ 멘션과 규칙 파일로 근거를 고정하며, 생성과 커밋 사이에 실행·테스트 단계를 반드시 두는 것이 이 글의 요지입니다. 처음에는 익숙한 작은 프로젝트에서 시작해, 결과를 검증할 수 있는 크기의 작업만 맡기는 것이 좋습니다.
더 읽을거리로는 GitHub Copilot 소개, Cursor 문서, 그리고 이 블로그의 프롬프트 엔지니어링 글을 참고하십시오.
자주 묻는 질문 (FAQ)
Q. 바이브 코딩을 처음 시작할 때 무엇부터 해야 하나요?
A. 도구 설치보다 먼저 정해야 할 것은 “AI에게 어디까지 맡길지”에 대한 자신만의 기준입니다. 처음에는 익숙한 작은 기능(간단한 CRUD, 유틸리티 함수)부터 AI에게 맡기고, 생성된 코드를 한 줄씩 읽으며 실행 결과를 직접 확인하는 습관을 들이는 것이 좋습니다. 이 과정을 건너뛰고 곧바로 복잡한 기능을 통째로 맡기면, 결과물이 그럴듯해 보여도 어디가 틀렸는지 판단할 감각이 없어 디버깅이 오히려 더 오래 걸리는 경우가 많습니다.
Q. AI가 만든 코드인지 리뷰어에게 알려야 하나요?
A. 팀 정책에 따라 다르지만, 커밋 메시지나 PR 설명에 AI 도구를 활용했다는 사실을 명시하는 팀이 늘고 있습니다. 이는 검열의 의미보다는, 리뷰어가 “왜 이런 패턴을 골랐는지”에 대한 설명이 부족할 수 있다는 점을 미리 인지하고 더 꼼꼼히 검토하게 만드는 효과가 있습니다. 결국 책임은 코드를 커밋한 사람에게 있으므로, AI가 만들었다는 사실이 검증 의무를 줄여주지는 않습니다.
같이 보면 좋은 글
- 개발자를 위한 AI 프롬프트 엔지니어링 | ChatGPT·Claude·Cursor 실전
- ChatGPT·LLM 코드 리뷰·리팩터링 심화 — 내부 메커니즘·정적 분석·컨텍스트·프로덕션 패턴