上周末刷 Hacker News,一篇 “Reports of code’s death are greatly exaggerated” 的帖子拿了 200 多个赞,评论区 200 多条,全在吵一个问题——Vibe Coding 到底靠不靠谱?

说实话,我自己就是 Vibe Coding 的重度用户。去年用 Cursor + Claude 写了好几个小工具,爽得不行。但上个月一个项目差点把我搞崩溃:AI 写的代码看着没问题,跑着也没问题,直到用户量上来之后……直接原地爆炸。

这篇文章我想聊聊自己的真实经历:Vibe Coding 确实能极大提升效率,但如果你不知道它的边界在哪里,翻车只是时间问题。

什么是 Vibe Coding?

“Vibe Coding” 这个词是 Karpathy 在 2025 年初提出来的——你不用逐行写代码,而是用自然语言描述你想要什么,AI 生成代码,你看一眼觉得"感觉对了"(the vibe is right),就直接用。

核心流程是这样的:

你的想法(模糊的) 
  → 自然语言描述 
  → AI 生成代码 
  → 你看效果 
  → "move the button there; make it bluer" 
  → 迭代

听起来很美好对吧?确实美好,但问题在于——你的"感觉对了",和代码真的对了,这之间隔着一条鸿沟。

翻车现场 #1:并发一上来就崩

这是我个人最惨痛的教训。

我让 AI 帮我写了一个文件处理服务,本地测试完美,代码结构清晰,还自带了漂亮的日志。部署上线后,5 个用户同时上传文件,服务直接挂了。

问题出在哪?AI 用了一个共享的临时目录来处理文件,没有加锁,也没有用唯一标识隔离不同用户的请求。单用户测试当然没问题,一旦并发就文件互相覆盖。

# AI 生成的代码(看着没毛病)
import os
import tempfile

TEMP_DIR = "/tmp/processor"

def process_file(uploaded_file):
    # 所有用户共享同一个目录!
    filepath = os.path.join(TEMP_DIR, "input.dat")  
    with open(filepath, "wb") as f:
        f.write(uploaded_file.read())
    result = do_heavy_processing(filepath)
    return result

修复其实很简单——用 tempfile.mkdtemp() 给每个请求创建独立目录:

import os
import tempfile

def process_file(uploaded_file):
    # 每个请求独立的临时目录
    work_dir = tempfile.mkdtemp(prefix="proc_")
    try:
        filepath = os.path.join(work_dir, "input.dat")
        with open(filepath, "wb") as f:
            f.write(uploaded_file.read())
        result = do_heavy_processing(filepath)
        return result
    finally:
        # 清理临时文件
        import shutil
        shutil.rmtree(work_dir, ignore_errors=True)

教训:AI 生成的代码默认是"单用户心智模型"。它不会主动考虑并发安全,除非你明确告诉它。

翻车现场 #2:错误处理只有 happy path

让 AI 写一个调用外部 API 的函数,它通常会给你一个很漂亮的实现——但只处理了成功的情况。

import requests

def get_weather(city: str) -> dict:
    response = requests.get(
        "https://api.weather.com/v1/current",
        params={"city": city, "key": "YOUR_API_KEY"}
    )
    data = response.json()
    return {
        "temperature": data["main"]["temp"],
        "description": data["weather"][0]["description"]
    }

看着没问题?那如果:

  • API 超时了呢?
  • 返回了 429 Too Many Requests 呢?
  • JSON 结构变了呢?
  • 网络断了呢?

加上错误处理后的版本:

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def get_weather(city: str) -> dict | None:
    session = requests.Session()
    retries = Retry(total=3, backoff_factor=0.5, 
                    status_forcelist=[429, 500, 502, 503])
    session.mount("https://", HTTPAdapter(max_retries=retries))
    
    try:
        response = session.get(
            "https://api.weather.com/v1/current",
            params={"city": city, "key": "YOUR_API_KEY"},
            timeout=10
        )
        response.raise_for_status()
        data = response.json()
        return {
            "temperature": data.get("main", {}).get("temp"),
            "description": (data.get("weather") or [{}])[0].get("description", "N/A")
        }
    except requests.exceptions.Timeout:
        logger.warning(f"Weather API timeout for city: {city}")
        return None
    except requests.exceptions.HTTPError as e:
        logger.error(f"Weather API error: {e}")
        return None
    except (KeyError, IndexError, ValueError) as e:
        logger.error(f"Unexpected response format: {e}")
        return None

教训:每次 AI 生成涉及网络请求、数据库操作、文件 I/O 的代码,第一件事就是检查错误处理是否完整。

翻车现场 #3:SQL 注入从天而降

这个真的吓出一身冷汗。我让 AI 帮我写一个简单的搜索功能,它给我拼了个字符串 SQL:

# 千万别这么写!
def search_users(query: str):
    sql = f"SELECT * FROM users WHERE name LIKE '%{query}%'"
    return db.execute(sql)

经典的 SQL 注入漏洞。如果 query 是 '; DROP TABLE users; --,数据库就没了。

正确做法:

def search_users(query: str):
    sql = "SELECT * FROM users WHERE name LIKE :query"
    return db.execute(sql, {"query": f"%{query}%"})

教训:AI 生成的代码中,安全问题是最隐蔽的。它不会主动告诉你"这里有注入风险"。涉及用户输入的地方,必须手动审查。

翻车现场 #4:状态管理变成一锅粥

让 AI 帮忙加功能的时候,它特别喜欢用全局变量或者把状态散落在各处。前三个功能还好,到第五个功能的时候,整个项目的状态流向就没人能看懂了。

这就是 Steve Krouse 说的"抽象泄漏"——Vibe Coding 让你觉得一切都很简单,直到复杂度积累到临界点,突然全盘崩溃。

解决方案

  1. 每加一个功能前,先让 AI 分析现有的状态管理结构
  2. 要求 AI 画出数据流图(用 Mermaid 或者 ASCII)
  3. 超过 3 个相关状态变量就应该抽成一个类或模块
# 散落的状态(危险)
user_cache = {}
rate_limits = {}
last_request_time = {}

# 封装后的状态(安全)
class APIClient:
    def __init__(self):
        self._cache: dict[str, Any] = {}
        self._rate_limits: dict[str, int] = {}
        self._last_request: dict[str, float] = {}
    
    def request(self, endpoint: str, **kwargs):
        self._check_rate_limit(endpoint)
        # ... 统一管理

翻车现场 #5:依赖版本地狱

AI 生成的代码经常混搭不同版本的库,或者用了已经废弃的 API。最常见的:

  • 用了 datetime.utcnow()(Python 3.12 已标记废弃)
  • 混用 asyncio.get_event_loop() 的新旧写法
  • Pydantic v1 和 v2 的 API 混着来

解决方案:项目初始化时,在 prompt 或项目配置里明确声明当前环境:

## 项目环境
- Python 3.12+
- Pydantic v2
- FastAPI 0.115+
- 使用 `datetime.now(timezone.utc)` 替代 `datetime.utcnow()`

把这段放在 Cursor 的 .cursorrules 或 Claude Projects 的 system prompt 里,AI 生成的代码质量会提升一个档次。

翻车现场 #6:测试?什么测试?

Vibe Coding 最大的问题之一:你让 AI 写了功能代码,但从来不让它写测试。或者让它写了测试,测试全是 mock,实际上什么都没验证。

# AI 写的"测试"(实际什么都没测)
def test_process_data():
    result = process_data(mock_input)
    assert result is not None  # 这叫测试??

正确的做法:让 AI 同时生成功能代码和测试代码,而且要求测试覆盖边界情况:

import pytest

class TestProcessData:
    def test_normal_input(self):
        result = process_data({"name": "test", "value": 42})
        assert result["status"] == "success"
        assert result["processed_value"] == 84
    
    def test_empty_input(self):
        with pytest.raises(ValueError, match="Input cannot be empty"):
            process_data({})
    
    def test_invalid_type(self):
        with pytest.raises(TypeError):
            process_data("not a dict")
    
    def test_missing_required_field(self):
        with pytest.raises(KeyError, match="value"):
            process_data({"name": "test"})
    
    def test_large_input(self):
        """确保不会因为大数据量 OOM"""
        large_input = {"name": "test", "value": 10**18}
        result = process_data(large_input)
        assert result["status"] == "success"

翻车现场 #7:不理解就不能 Debug

这是最根本的问题。当 AI 写的代码出了 bug,如果你完全不理解它的实现逻辑,debug 会变成一场噩梦。

你跟 AI 说"这里有 bug",它改了一版——引入了新 bug。你再说"还是不对"——它改回了第一版。来回几轮,你比自己从头写还累。

我的工作流

  1. AI 生成代码后,花 5 分钟通读一遍,确保理解核心逻辑
  2. 不理解的部分,让 AI 解释(不是让它重写)
  3. 如果涉及复杂算法,让 AI 加注释解释每一步
  4. 关键模块保持代码简洁——如果一个函数超过 50 行,让 AI 拆分

我的 Vibe Coding 最佳实践

经历了这些翻车之后,我总结了一套还算靠谱的工作流:

提示词模板

## 需求
[描述你要什么]

## 约束
- 必须处理错误情况:超时、网络异常、无效输入
- 使用参数化查询,不允许 SQL 拼接
- 并发安全(如果涉及共享资源)
- 包含完整的类型注解
- 附带单元测试,覆盖正常和边界情况

## 环境
- Python 3.12+, FastAPI, Pydantic v2

Checklist

每次 AI 生成代码后,过一遍这个清单:

  • 错误处理是否完整?
  • 有没有安全漏洞(注入、路径穿越等)?
  • 并发场景下是否安全?
  • 依赖的库版本是否正确?
  • 是否有测试覆盖?
  • 核心逻辑我是否理解?

调用 API 的代码示例

对于需要调用大模型 API 的项目,我一般这么写:

from openai import OpenAI

client = OpenAI(
    api_key="your-api-key",
    base_url="https://api.ofox.ai/v1"  # 国内直连
)

response = client.chat.completions.create(
    model="claude-sonnet-4-20250514",
    messages=[
        {"role": "system", "content": "你是一个代码审查助手"},
        {"role": "user", "content": f"请审查以下代码的安全性和健壮性:\n{code}"}
    ],
    temperature=0.3
)

review = response.choices[0].message.content

用 AI 来审查 AI 生成的代码——这才是 2026 年的正确打开方式。

写在最后

Vibe Coding 不是洪水猛兽,也不是银弹。它是一个超强的效率工具,但前提是你得知道它的边界。

引用 Dijkstra 的话:“抽象的目的不是变得模糊,而是在新的语义层面上保持精确。”

AI 帮你生成代码是抽象的一种形式——但如果你放弃了对精确性的追求,迟早会被复杂度反噬。

最好的状态是:让 AI 处理 80% 的重复劳动,你把精力集中在 20% 的架构设计和边界思考上。 这才是 Vibe Coding 应该有的样子。


你在 Vibe Coding 的过程中踩过什么坑?欢迎在评论区分享你的翻车经历 👇

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐