昨天下午花了大概 3 个小时用 Claude Code + DeepSeek V3.2 写了个简单的 CLI,大概的效果是这样:
效果示例
> things-cost
物品日均花费统计工具
Usage: things-cost <COMMAND>
Commands:
add 添加新物品
list 列出所有物品及其日均成本
update 更新物品信息
delete 删除物品
discard 弃用物品
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
> things-cost list
+----+-------------+------------+------------+--------------+----------+-------------+
| ID | 名称 | 起始日期 | 弃用日期 | 价格 | 使用天数 | 日均成本 |
+----+-------------+------------+------------+--------------+----------+-------------+
| 2 | iPhone | 2020-10-01 | 2023-12-31 | 3000.00 CNY | 1156 | 2.5952 CNY |
+----+-------------+------------+------------+--------------+----------+-------------+
| 1 | MacBook Pro | 2021-01-01 | - | 12000.00 CNY | 1234 | 9.7245 CNY |
+----+-------------+------------+------------+--------------+----------+-------------+
起步不错
效果还不错,是吧?如果我说,我只是写了一个简单的 20 行的 CLAUDE.md,然后让 DeepSeek 自行发挥一轮就得到了一个模块划分良好,已经 95% 能呈现成上面这个样子的版本,总耗时不过是 5 分钟的话,这篇文章是不是几乎完全是一篇大模型推广文的味道了?
初始提示词
# 物品日均花费统计
简单统计一下当前物品的日均消费成本,例如,一台手机是 2020 年 1 月 1 日花费 3000 元购买,到 2022 年 1 月 1 日,其日均成本为 3000 / (366 + 365)
## 应用架构设计
该应用采用三层架构设计:
- **数据层**: 保存在遵从 *nix XDG 标准的目录下的 SQLite 数据库,向应用层暴露数据获取接口。应用层不需要关心数据是如何存储的。
- **应用层**: 处理业务逻辑,从数据层获取数据、结合今日日期换算成具体的条目,计算日均成本。
- **视图层**: 处理用户交互,调用应用层的 Service,展示和增加、修改条目。
### 应用数据模型
- `name`: 物品名称
- `start_date`: 起始日期
- `discard_date`: 弃用日期 (可选 NaiveDate)
- `price`: 价格 (f64)
- `currency`: 币种 (String,默认 CNY)
但这五分钟之后,我继续花费的三个小时,才让我感受到为什么大模型“还不是很行”。这三个小时里,我做了几个简单的迭代:
- 增加了
things-cost discard命令,用于设置该物品已经被弃用 - 调整了
things-cost update命令的参数 从原本的things-cost update <ID> [NAME] [START_DATE] [PRICE] [CURRENCY] [DISCARD_DATE],调整为了things-cost update <FIELD> <ID> <VALUE> - 增加了“卖出价格”字段
- 支持通过 Github Action 发布 Release
前两个迭代都很顺利地完成了,只是在第二次迭代的最后遇到了一点小问题,上下文爆了。这个时候我甚至不能通过 /compact 来压缩上下文,因为执行 /compact 也会爆(我在第一轮完成之后已经 /compact 过一次了,并且让 LLM 更新过一次 CLAUDE.md)。只能退出重来,这样就丢失了中间的这些执行记录,不过问题不大,我们的代码很简单,而且已经有一个能基本解释功能的 CLAUDE.md 了。
LLM 的局限
当我让大模型增加一个新字段的时候,问题就产生了。我让大模型给这个应用增加一个字段,它就真的只是在数据层(数据库、Model 定义)增加了一个新字段,没有考虑数据库迁移,也没有考虑使用到这个 Model 的场景也需要更新。于是我需要一次次地枚举“给这里也增加一下 discard_date 字段的支持”,大模型才会一遍遍地给我补充代码,补充单测。
最后一个问题就比较磨人了,也让我充分感受到了 LLM 的局限。简单总结一下,限制 LLM 发挥的有三个部分:我对当前问题的认知水平、它自己的认知能力,以及它所获得的知识量。
大模型给我的第一个版本是一个 ci.yaml,on:push 触发,会自动 Test、Build,然后尝试创建 Release。但没有自动创建新的 Tag,Release 是创建不了的。
第二个版本添加了一个 release.yaml 改成了 on:tag,Push 上去构建直接就是一个全部失败:
error occurred in cc-rs: failed to find tool "x86_64-linux-musl-gcc": No such file or directory (os error 2)
这里我感觉到一次到位似乎对 LLM 来说太过困难,想了想,没必要在 CI 流水线 Build,在 Release 流水线再 Build 一次,能不能让 Release 流水线复用 CI 的制品?大模型:我明白了!于是在第三个版本中,它删掉了 Release 的所有 Build,保留了一个 download_artificial。显而易见,它又失败了:download_artificial 没有填写参数,默认情况下不会跨 Workflow 下载制品。
于是我让 LLM 给出第三个版本,把 ci.yaml 的 Build 步骤去掉,移动到 Release 流水线内。终于,它“几乎”修好了,所有 Job 都运作正常,只是在 Upload Release 这一步失败了,因为它分别构建了 Linux、macOS 和 Windows 的结果,而这三个环境的二进制文件是重名的……
- name: Create Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.ref_name }}
name: Release ${{ github.ref_name }}
body: |
Automated release for ${{ github.ref_name }}
files: |
artifacts/binary-ubuntu-latest-x86_64/things-cost
artifacts/binary-macos-latest-x86_64/things-cost
artifacts/binary-macos-latest-aarch64/things-cost
artifacts/binary-windows-latest-x86_64/things-cost.exe
draft: false
prerelease: false
到了这一步,它就无论如何无法找到这个重名问题了,而我的认知也无法覆盖到这个问题,暂时就只能停滞在这里。这就是我的认知水平限制了大模型的输出能力。
不过这里还有一个问题,softprops/action-gh-release 这个 Action 是有 v2 的,而 DeepSeek 使用了 v1,大概是因为它参考知识库中的代码使用了 v1 版本。如果它使用了 v2,我就不会那么困惑,因为 v2 虽然依然会报错,但是会将不重名的部分上传到 Github Release,然后就应当能够发现这个重名问题。我理解这是大模型所获得的知识量限制了它的能力。
总结
在当前阶段,我对 Vibe Coding 的观点是,如果以我目前的认知水平无法独立完成的任务,就不应该指望大模型能帮我完成;如果我不知道应该用什么工具,我最好不要完全信任大模型帮我选择的工具,大概率会是过时的。
- 大模型出错时,我无法保证能及时发现并提示大模型改正。
- 遇到卡点时,我无法知道正确的解决方法,而大模型经常给出一些不靠谱的“猜测”方案,并且用非常确定的语气开始执行。
- 大模型使用过时的包或者工具编写的代码很可能不可靠,而我无法指望它还能拥有新版本相关的知识(所以我不能直接让大模型升级这个包)。