코딩 몰라도 짭니다" — 바이브 코딩의 진짜 현실과 내 생각

바이브 코딩(Vibe Coding)의 개념과 장단점을 넘어, 실제 코드 예시를 통해 "아는 만큼 바이브 코딩이 된다"는 현실적 한계를 보여주고, 진짜 필요한 자세를 정리.
마크다운 원문·이 글에 보충할 내용이 있나요?

"코딩 몰라도 짭니다" — 바이브 코딩의 진짜 현실

1. 바이브코딩이란

바이브코딩(Vibe Coding)은 Andrej Karpathy(Tesla AI 전 소장)가 처음 사용한 용어로, 코드를 직접 짜는 대신 AI에게 자연어로 지시하여 프로그램을 만드는 개발 방식이다.

"이런 느낌의 앱을 만들어줘"라고 말하면 AI가 코드를 작성하고, 에러가 나면 에러 메시지를 AI에게 붙여넣으면 알아서 고쳐준다. 코드의 "느낌(vibe)"에 집중하고, 내부 구현은 AI에게 위임한다.

왜 화제가 되었나

  • 진입장벽 붕괴: 프로그래밍 언어를 몰라도 앱을 만들 수 있게 되었다
  • 속도: 전통적 개발 대비 10~100배 빠른 프로토타이핑
  • 도구 발전: Claude Code, Cursor, Codex, Qoder 등 에이전틱 IDE의 등장

2. 코드로 보는 바이브 코딩의 현실

이론만으로는 왜 바이브 코딩이 위험한지 와닿지 않는다. 실제 코드 예시를 통해 보자.

예시 1: "로그인 페이지 만들어줘"

AI가 만든 코드 (비전공자가 받은 결과물):


from flask import Flask, request, jsonify
import sqlite3
import hashlib

app = Flask(__name__)

@app.route('/login', methods=['POST'])
def login():
    username = request.json['username']
    password = request.json['password']

    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()

    # 여기가 문제다
    query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
    cursor.execute(query)
    user = cursor.fetchone()

    if user:
        return jsonify({"status": "success"})
    else:
        return jsonify({"status": "fail"})

비전공자의 반응: "오 된다! 로그인 성공하면 success 뜨네."

코딩을 아는 사람의 반응: "이건 SQL 인젝션이다."

누군가 username' OR '1'='1'를 입력하면 데이터베이스의 모든 사용자 데이터가 유출된다. AI가 만들어준 코드가 "동작"은 하지만, 보안 취약점이 있다. 비전공자는 이 사실을 모른다.

바른 코드:


@app.route('/login', methods=['POST'])
def login():
    username = request.json['username']
    password = request.json['password']

    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()

    # 바인딩 사용 → SQL 인젝션 방지
    cursor.execute(
        "SELECT * FROM users WHERE username=? AND password=?",
        (username, hashlib.sha256(password.encode()).hexdigest())
    )
    user = cursor.fetchone()
    # ...

차이를 설명할 수 있는가? 이것이 "아는 만큼"의 갭이다.


예시 2: "이미지 업로드 기능 만들어줘"

AI가 만든 코드:


@app.route('/upload', methods=['POST'])
def upload():
    file = request.files['image']
    file.save(f'/static/uploads/{file.filename}')
    return jsonify({"url": f'/static/uploads/{file.filename}'})

비전공자의 반응: "이미지 업로드 되네! 좋다."

코딩을 아는 사람의 반응: "이건 서버가 터질 수 있다."

문제점 3가지:

  1. 파일명 충돌: 같은 이름 파일을 올리면 덮어쓰기
  2. 파일 종류 제한 없음: .exe, .sh 등 실행 파일도 업로드 가능
  3. 크기 제한 없음: 10GB 파일을 올리면 디스크가 가득 참

바른 코드:


import os
import uuid
from werkzeug.utils import secure_filename

ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
MAX_FILE_SIZE = 10 * 1024 * 1024  # 10MB

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/upload', methods=['POST'])
def upload():
    file = request.files['image']

    if not file or not allowed_file(file.filename):
        return jsonify({"error": "허용되지 않는 파일 형식"}), 400

    if file.content_length and file.content_length > MAX_FILE_SIZE:
        return jsonify({"error": "파일 크기 초과"}), 400

    # 고유 파일명 생성 (덮어쓰기 방지)
    ext = file.filename.rsplit('.', 1)[1].lower()
    filename = f"{uuid.uuid4().hex}.{ext}"
    filepath = os.path.join('/static/uploads', secure_filename(filename))

    file.save(filepath)
    return jsonify({"url": f'/uploads/{filename}'})

이 코드의 7줄짜리 로직을 설명할 수 있는가? 이것이 "아는 만큼"이다.


예시 3: "데이터베이스에서 사용자 목록 가져와줘"

AI가 만든 코드:


@app.route('/users')
def get_users():
    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users")
    users = cursor.fetchall()
    return jsonify(users)

비전공자의 반응: "사용자 목록이 나온다!"

코딩을 아는 사람의 반응: "이건 100만 명이면 서버가 죽는다."

문제점:

  1. 페이지네이션 없음: 100만 행을 한 번에 가져옴 → 메모리 폭발
  2. 비밀번호 포함: SELECT *이 비밀번호 해시까지 가져옴
  3. 커넥션 미반환: conn.close() 없음 → DB 연결 누수

바른 코드:


@app.route('/users')
def get_users():
    page = int(request.args.get('page', 1))
    per_page = int(request.args.get('per_page', 20))
    offset = (page - 1) * per_page

    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()

    # 비밀번호 제외, 페이지네이션 적용
    cursor.execute(
        "SELECT id, username, email, created_at FROM users LIMIT ? OFFSET ?",
        (per_page, offset)
    )
    users = cursor.fetchall()

    # 총 개수 조회 (페이지네이션용)
    cursor.execute("SELECT COUNT(*) FROM users")
    total = cursor.fetchone()[0]

    conn.close()

    return jsonify({
        "users": users,
        "total": total,
        "page": page,
        "per_page": per_page
    })

SELECT \*이 왜 위험한지, LIMIT/OFFSET이 왜 필요한지 설명할 수 있는가?


예시 4: "SPA(단일 페이지 앱) 만들어줘"

AI가 만든 HTML/JS:


<!DOCTYPE html>
<html>
<head>
    <title>내 앱</title>
</head>
<body>
    <div id="app"></div>
    <script>
        // 라우터 구현
        function navigate(path) {
            fetch(path)
                .then(res => res.text())
                .then(html => {
                    document.getElementById('app').innerHTML = html;
                });
        }

        // 모든 링크에 이벤트 바인딩
        document.addEventListener('click', e => {
            if (e.target.tagName === 'A') {
                e.preventDefault();
                navigate(e.target.href);
            }
        });
    </script>
</body>
</html>

비전공자의 반응: "오SPA가 되네! 페이지 전환도 자연스럽고."

코딩을 아는 사람의 반응: "XSS 취약점이고, 상태 관리가 없다."

문제점:

  1. XSS(Cross-Site Scripting): innerHTML에 사용자 입력을 그대로 넣으면 악성 스크립트 삽입 가능
  2. 상태 관리 없음: 사용자가 로그인했는지, 어떤 페이지인지 알 수 없음
  3. 에러 핸들링 없음: 네트워크 에러 시 화면이 깨짐

간단한 개선:


function navigate(path) {
    fetch(path)
        .then(res => {
            if (!res.ok) throw new Error(`HTTP ${res.status}`);
            return res.text();
        })
        .then(html => {
            // XSS 방지: textContent 사용
            document.getElementById('app').textContent = '';
            const div = document.createElement('div');
            div.innerHTML = html;  // 서버에서 신뢰할 수 있는 경우만
            document.getElementById('app').appendChild(div);
        })
        .catch(err => {
            document.getElementById('app').textContent =
                '오류가 발생했습니다: ' + err.message;
        });
}

innerHTML의 위험성을 설명할 수 있는가?


예시 5: "이메일 발송 기능 만들어줘"

AI가 만든 코드:


import smtplib

@app.route('/send-email', methods=['POST'])
def send_email():
    to = request.json['to']
    subject = request.json['subject']
    body = request.json['body']

    server = smtplib.SMTP('smtp.gmail.com', 587)
    server.starttls()
    server.login('myemail@gmail.com', 'mypassword')
    server.sendmail('myemail@gmail.com', to, f'Subject: {subject}\n\n{body}')
    server.quit()

    return jsonify({"status": "sent"})

비전공자의 반응: "이메일 보내진다! 좋다."

코딩을 아는 사람의 반응: "비밀번호가 코드에 있다. 보안 침해다."

치명적 문제:

  1. 비밀번호 하드코딩: 소스코드에 비밀번호가 그대로 노출
  2. SMTP 자격증명 로테이션 없음: 비밀번호가 바뀌면 전체 시스템 중단
  3. 수신자 검증 없음: 아무 이메일이나 보낼 수 있음

바른 코드:


import os
import smtplib
from dotenv import load_dotenv

load_dotenv()  # .env 파일에서 읽기

@app.route('/send-email', methods=['POST'])
def send_email():
    to = request.json['to']
    subject = request.json['subject']
    body = request.json['body']

    # 환경변수에서 비밀번호 로드
    smtp_user = os.environ.get('SMTP_USER')
    smtp_pass = os.environ.get('SMTP_PASS')

    server = smtplib.SMTP('smtp.gmail.com', 587)
    server.starttls()
    server.login(smtp_user, smtp_pass)
    server.sendmail(smtp_user, to, f'Subject: {subject}\n\n{body}')
    server.quit()

    return jsonify({"status": "sent"})

.env 파일이란 무엇인지, 왜 필요한지 설명할 수 있는가?


3. 명확한 장단점

장점

  • 비전공자도 가능: 프로그래밍 언어 없이도 기능하는 앱을 만들 수 있다
  • 프로토타이핑 극단적 가속: 아이디어를 당일에 구현 가능
  • 반복 작업 자동화: 보일러플레이트 코드, 테스트, 문서화를 AI에게 위임

단점

  • 검증 불가능: AI가 만든 코드가 왜 동작하는지 이해하지 못하면, 에러 발생 시 대처 불가
  • 시간 낭비 함정: "아무거나 만들어주면 되지"라는 생각으로 접근하면, 수정과 디버깅에 더 많은 시간을 쏟는다
  • 사고 붕괴: 논리적 사고 과정을 건너뛰면, 정작 문제 해결 능력이 오히려 떨어진다
  • 보안 취약점: AI가 만든 코드의 보안 이슈를 검증할 수 없으면, 프로덕션에 배포할 수 없다

4. 전통적 코딩 vs 바이브 코딩

항목전통적 코딩바이브 코딩
핵심 도구프로그래밍 언어프롬프트 + AI 에이전트
개발 방식알고리즘 직접 설계생성 위임
진입장벽높음 (수개월~수년)낮음 (자연어)
최대 강점완벽한 제어, 에러 추적빠른 프로토타이핑
핵심 역량코드 구문, 최적화프롬프트 엔지니어링
검증 능력있음 (직접 작성)없음 (AI 위임)

5. 내 생각 — "아는 만큼 바이브 코딩이 된다"

솔직히 말하겠다.

바이브 코딩은 "코딩을 모르는 사람을 위한 구원"이 아니다. 그것은 과장이다.

아는 만큼이 전부다

AI에게 "로그인 페이지 만들어줘"라고 했을 때, 나는 Python을 알기에 AI가 만든 코드에 SQL 인젝션이 있는지 확인할 수 있다. HTML/CSS를 알기에 결과물이 제대로 되었는지 눈으로 볼 수 있다. JavaScript를 알기에 왜 안 되는지 추적할 수 있다.

그런데 프로그래밍을 모르는 사람이 같은 지시를 내리면? AI가 만든 코드가 보안상 안전한지, 성능 문제가 있는지, 에러 핸들링이 되어 있는지 판단할 수 없다. AI가 "완성되었다"고 하면 그냥 믿어야 한다. 이것이 바로 바이브 코딩의 치명적 한계다.

레벨로 보는 바이브 코딩 현실


Level 1: "이거 만들어줘"
→ AI가 만듦 → 결과물 확인 불가 → 대부분 실패
→ 비유: 건축 지식 없이 "집 지어줘" → 엉뚱한 집이 나옴

Level 2: "이 부분이 안 돼, 고쳐줘"
→ AI가 수정 → 부분적 확인 가능 → 가끔 성공
→ 비유: "문이 안 열려"라고 말할 수는 있음 → 수리 가능

Level 3: "이 코드에서 인증 토큰 유효성 검사가 빠졌어"
→ AI가 수정 → 논리 검증 가능 → 대부분 성공
→ 비유: "문 잠금 장치가 고장났다"고 정확히 진단 가능 → 수리 확실

Level 4: "이 아키텍처에서 동시성 제어가 필요해, 데드락 방지 포함"
→ AI가 설계 → 최적화 가능 → 완성
→ 비유: 건축가 수준의 설계 능력 → 완벽한 집

Level 1에서 Level 2로 올라가려면 최소한 "무엇이 잘못되었는지"를 설명할 수 있어야 한다. 이것이 바로 "아는 만큼"이다.

레벨별 "아는 것"의 차이

레벨"아는 것"의 예시바이브 코딩 활용도
1"컴퓨터를 켤 줄 안다"幾乎 0%
2"파일을 열고 닫을 줄 안다"10~20%
3"데이터베이스가 뭔지 안다"30~50%
4"네트워크/보안이 뭔지 안다"50~70%
5"아키텍처/설계 패턴을 안다"70~90%

레벨이 높을수록 AI와의 협업이 효율적이다. AI는 "지식의 승수"이지, "지식의 대체재"가 아니다.

6. 그렇다면 어떻게 해야 하나

① 포기하지 마라

바이브 코딩이 대세가 되었다고 코딩 공부를 포기하면 안 된다. 오히려 역설적으로, 바이브 코딩을 제대로 활용하려면 코딩 지식이 더 필요하다.

② "왜"를 물어라

AI가 만든 코드를 그냥 쓰지 마라. "왜 이렇게 했어?"라고 물어보라. AI의 설명을 이해하는 과정에서 실력이 쌓인다.

③ 에러를 두려워하지 마라

에러 메시지를 AI에게 붙여넣기 전에, 한번 읽어보라. "FileNotFoundError", "IndexError", "Timeout" — 이 단어들을 알면 이미 Level 2다.

④ 프롬프트 엔지니어링만으로는 부족하다

프롬프트를 잘 쓰는 것도 중요하지만, 그 너머에 "왜 이 프롬프트가 효과적인지"를 이해하는 도메인 지식이 있어야 한다.

7. 결론

바이브 코딩은 강력한 도구다. 그러나 도구는 손을 가진 사람의 수준만큼만 작동한다.

> "아는 만큼 바이브 코딩이 된다. 더도 말고 덜도 말고, 내가 아는 만큼."

SQL 인젝션이 뭔지 모르면, AI가 만들어준 로그인 페이지가 당신의 사용자 데이터를 통째로 빼낼 수 있다. XSS가 뭔지 모르면, AI가 만들어준 웹사이트가 악성 스크립트의 온상이 될 수 있다.

AI가 코딩을 대체하는 시대가 왔다고 하지만, 진짜 대체되는 것은 "단순 반복 코딩"이다. 문제를 이해하고, 설계하고, 검증하는 능력은 여전히 인간의 몫이다.

바이브 코딩을 한다면, 적어도 "이것이 왜 잘못되었는지"를 말할 수 있어야 한다. 그렇지 않으면 당신은 AI의 고객이 아니라, AI의 노예가 되는 것이다.


본 글은 운영자 개인의 실전 경험과 관점을 바탕으로 작성되었습니다.

👁 조회 2 · 💬 댓글 0개 · 작성자 유형: human | 빌드: 2026-09-23T15:25:41+09:00