Vibe Coding 实战:什么时候该放手,什么时候必须盯着
“凭感觉写代码”最容易被误解成“不用理解代码”。我这一年的体会恰好相反:放手的前提,是你有一套不依赖读源码、也能判断“这东西对不对”的办法。这篇记录我现在用的做法。
1. 先划边界:什么活适合放手
我用“错了的代价”和“能不能快速验证”两个维度给任务分类:
| 验证快 | 验证慢 | |
|---|---|---|
| 代价低 | 完全放手:一次性脚本、格式转换、小页面、数据清洗 | 放手,但留好回退:定时任务、报表生成 |
| 代价高 | 放手写,我来验:解析器、计费公式、权限判断 | 不放手:数据删除、迁移、权限与认证、涉及钱的逻辑 |
第一格的活,我基本一句话描述完需求就开始运行结果;最后一格的活,模型只负责起草和解释,决定由我下。大部分日常工作落在中间两格,这也是下面这套流程主要解决的问题。
2. 工作循环:切片、运行、验证
一次性让模型“把整个系统写出来”,基本一定会得到一个看起来完整、实际每个环节都有点问题的东西,而且问题互相掩盖,很难定位。我现在的循环是:
- 写一页需求:输入是什么、输出是什么、不做什么。哪怕只有五行。
- 切成能独立运行的小片:每一片做完都能单独跑出结果。比如“读文件并打印前 10 条记录”就是第一片,而不是“完成解析器”。
- 每片做完先运行,再往下走:不要攒着一起测。
- 每片用一个验证手段确认(见下一节),通过了才提交。
- 提交一次,写一句话说明:出了问题可以二分回退。
3. 验证阶梯:从便宜到昂贵
验证方式按成本排成一个阶梯,能用低的就不用高的,但高风险的片必须往上爬。
第 1 级:运行它
最便宜,也最容易被跳过。模型说“已完成”不算数,亲眼看到输出才算。
第 2 级:看输出的“形状”
不看每个值,只看总体是否合理:行数、取值范围、有无空值、顺序是否符合预期。几行断言就能做到:
import pandas as pd
df = pd.read_csv("out.csv")
assert len(df) > 0, "结果为空"
assert df["voltage"].between(2.5, 4.3).all(), "电压越界,可能字节序或比例错了"
assert df["seq"].is_monotonic_increasing, "序号倒退,可能是记录对齐错位"
assert df.isna().mean().max() < 0.01, "有列大量为空"
print("形状检查通过:", len(df), "行")
这类断言写起来快,却能抓住“代码能跑、结果全错”的大部分情况。
第 3 级:用已知答案对照
找几条你手工能算出结果的样本,让程序跑一遍,对比。我习惯留一个 fixtures/ 目录,放 3 到 5 个输入和人工核对过的期望输出,每次改动后全部过一遍。
第 4 级:不变量与反向检查
写出“无论输入是什么,这一定成立”的性质,例如“拆分后各部分之和等于总数”“排序后再排序结果不变”。对解析、统计、金额类逻辑特别有效,因为模型最容易在边界条件上出错。
第 5 级:读 diff
最贵,留给高风险的片。读 diff 时我只问三件事:它改了哪些我没让它改的文件?有没有新增依赖?有没有吞掉异常?
4. 四类静默失败
vibe coding 的麻烦不在于报错,报错好办。麻烦在于“静默”:程序不崩,结果却不对。我总结了最常见的四类。
① 编造接口
调用一个听起来存在、实际不存在的函数或参数,或者用了旧版本的写法。对策:涉及第三方库或 API 的片,让模型先贴出它依据的官方文档链接或版本号,第一次运行时留意告警(尤其是 DeprecationWarning)。
② 悄悄兜底
遇到异常时返回一个默认值,而不是报错。例如:
# 看起来很稳健,实际会把所有失败伪装成“0 条数据”
try:
rows = parse(path)
except Exception:
rows = []
对策:在需求里写明“遇到无法处理的输入必须报错并带上位置信息,不要吞异常”;读 diff 时专门搜 except 和 or []、or 0。
③ 为通过测试而写
模型发现测试过不了,就去改测试,或者针对测试用例写死答案。对策:测试和期望输出由我提供,且告诉模型“不允许修改 fixtures/ 与测试文件”。
④ 范围蔓延
让它改一处,它顺手“优化”了十处:重命名变量、换库、调整目录结构。每一处单看都合理,合起来你已经无法审阅。对策:每次提示都写“只改某某文件,不要重构”;提交前看 git diff --stat,改动文件数超出预期就退回重来。
5. 上下文管理:让模型“记得”项目
模型没有跨会话的记忆,每次对话都像新来的同事。我用两个办法弥补:
- 项目根目录放一份简短的说明文件(我叫
CLAUDE.md):技术栈、目录结构、怎么运行与测试、不要做什么。不超过一屏。这份文件的价值在“约束”而不在“介绍”。 - 把踩过的坑写成一句话规则放进去。例如“日志文件不要按时间戳排序,设备时钟会复位”“数据库放在网站目录之外”。同一个坑不该踩两次,也不该让模型再踩一次。
反过来,对话越长越要警惕。会话里塞满了过期的尝试、被推翻的方案,模型会被这些噪声带偏。我的习惯是:一个功能片做完提交后,开新会话再做下一片,只带说明文件和必要的代码。
6. 提示词的写法
我现在写需求基本按下面这个模板,写得越具体,返工越少:
目标:把 input/*.csv 合并成一个按日期排序的 out.csv
输入:每个文件第一行是表头,列为 date,device,value;date 格式 YYYY-MM-DD
输出:同样三列;重复行(date+device 相同)保留 value 较大的一条
约束:
- 只改 merge.py,不要新增依赖,不要改 fixtures/
- 遇到格式不对的行:停止并报错,带上文件名和行号,不要跳过
验收:python merge.py && python check_shape.py 通过;fixtures/ 下 3 个样例输出与期望一致
其中“验收”一行最关键:它把“做完了”从模型的主观判断,变成一个可以运行的命令。
7. 必须亲自盯着的地方
- 认证与权限:谁能看什么、谁能改什么。模型写的权限判断常常“看起来对”,但边界情况(空值、大小写、越权访问他人记录)容易漏。这部分我逐行读,并手动用不同身份测试。
- 涉及删除和覆盖的操作:先让它只打印“将要删除什么”,确认后再执行。
- 密钥与凭据:永远不让密钥出现在提示词、代码和提交里。用环境变量或单独的配置文件,并确认已加入忽略列表。
- 金额与计费公式:用手算的样本逐条核对,不信“看起来对”。
- 对外暴露的接口:输入校验、速率限制、日志里不能带敏感信息。
8. 一页检查清单
| 阶段 | 问自己 |
|---|---|
| 开始前 | 这活错了的代价多大?能快速验证吗?需求写成一页了吗? |
| 每一片 | 能单独运行吗?有一个可执行的验收命令吗? |
| 每次提交前 | git diff --stat 改动范围符合预期吗?有吞异常吗?有新依赖吗? |
| 上线前 | 密钥在代码外吗?危险操作有“先预览”吗?权限用不同身份测过吗? |
| 出问题后 | 是哪一片?能二分回退吗?这个坑写进说明文件了吗? |
最后一句:vibe coding 把“写代码”的成本压得很低,但把“判断对不对”的成本整个留给了你。想清楚怎么验证,比想清楚怎么写更重要。