利益相关声明:作者与文中产品有直接的利益相关(开发者、自家产品等)

过去两个月里,我做了一款 macOS 原生离线 EPUB 阅读器,名字叫 Silverlinger(中文名“余阅”,项目最初的工程名叫 EPUBReader)。

就在前两天,它的 1.6.3 稳定版(Build 76) 正式发布了,官网公证包与 Mac App Store 版本悉数就位。现在我自己每天都在用它看书,无论是翻页的跟手程度、排版带来的呼吸感,都到了一个让我自己很满意的状态。

但说出来你可能不信:从第一天立项,到仓库里攒下 70 个版本的迭代档案、Build 号推进到 76,我在整个项目里没有亲手写过一行代码。

我的背景可能跟正在看这篇文章的很多人一样,有产品经理的思维,听得懂技术概念,早些年也摸过代码皮毛。但是,有朝一日,单枪匹马用 Swift 加上 WebKit 去写一个 Mac 桌面软件?——我从来没想过,做梦都没想过。

而今天,我居然做到了。后来粗略一查,仅留下记录的部分,AI 累计处理的 Token 就超过了 11 亿。这里面绝大多数,是它在工作中反复翻阅项目资料和对话记录的消耗。

不要以为 Vibe Coding 就是泡杯咖啡,对着对话框许愿:“帮我写个像 Apple Books 一样的阅读器。”

如果你真的这么干,前三天你会觉得自己是无所不能的造物主;到了第四天,你会发现改一个按钮颜色会导致翻页彻底失灵;到了第七天,AI 会陷入“修好 A 搞坏 B、修好 B 又把 A 搞坏”的鬼打墙,而你因为看不懂底层代码,只能绝望地看着屏幕干瞪眼。

许愿式编程的成果,自用或许可以,可是想作为“产品”交付用户,那是万万不行的。

接下来,我和大家分享我的经验:作为一个不写一行代码的负责人,我是怎么从最初的手忙脚乱,一步步把一群 AI 管明白,最终打磨出这款产品的。


一、 认清自己的位置:“带团队的产品总监”

既然你不写代码,就千万不要试图去跟 AI 比拼语法细节。你真正的价值只有三件事:定规矩、控范围、做裁决。

在 Silverlinger 的开发后期,AI 在我眼里不再是一个随便聊天的对话框,而是一支职责分明的虚拟研发团队:

  • 主代理(架构师兼项目经理):Claude Code(Opus 模型) 负责跟我对齐需求、查源码、写方案、把控节奏;
  • 执行代理(一线开发):Codex (Sol模型) 拿到批准后的方案,专门负责动手改代码;
  • 评审团:Codex(Astra模型)、GLM、Gemini只看不写,专门给主代理的方案挑刺;
  • 代码审计员(code-auditor):执行代理的代码写完后,Claude Code(Opus 模型) 专门做只读体检。

在这个团队里,我是“产品总监”。从小版本立项、实现、自动化测试、人工测试,到封版发布,每一个关键节点都有明确的授权边界——AI 必须等我敲下那句“批准”或“授权”,才会继续推进。

当然,这样做或可以解释为我能力不足,因为我经常看到有些人放任 AI 跑一个晚上写代码,我想他们一定有比我更好的工作流设计。


二、 给 AI 立一部“家法”,治疗它的健忘与自作聪明

AI 最大的毛病有两个:一是换个对话就“失忆”,二是总喜欢自作聪明地帮你“重新发明轮子”。

为了治这个病,我让 AI 自己维护了一套极严密的“项目家法”(写入根目录 AGENTS.md 和 Docs/README.md)。

  1. 让 AI 进门先看“唯一路标”:文档最顶部永远有一行字,指明当前在哪一阶段、正在做哪个版本。任何新开启的对话,AI 第一件事就是读这个路标,有据可依地接上手头的活,绝不允许跨界乱改。
  2. 把教训写成“死规矩”:每当我在重大方向上做了取舍,就让 AI 记录一份“架构决策档案(ADR)”。比如最早的 V1 版本,我跟风做了一个“书架”界面;后来自己用着实在别扭——Mac 用户最自然的习惯就是在 Finder 里双击电子书直接阅读,再搞个书架管理纯属脱裤子放屁。于是在 V2 我果断把书架连根拔起,并在家法里写死:“Finder 就是唯一的书架,往后无论谁写代码,绝对禁止再把书架和自建的历史列表加回来。”
  3. 划清“楚河汉界”:虽然我不写代码,但我懂分工的道理。我在规则里锁死:原生外壳归 SwiftUI 管,排版渲染归 JS 引擎管,读书进度和笔记归本地存储管。谁也别抢谁的活。

三、 别信 AI 拍胸脯,遇到不确定,先让它“跑个小实验”

我们最容易被 AI 忽悠的时刻,就是它信誓旦旦地跟你说:“没问题,我马上改。”结果改完一跑,一堆 Bug。

吃过几次亏后,我立下了一个规矩:自己不确定的,就先让 AI 写个不启动主程序的“小实验(探针 / Spike)”去摸底。

举两个例子:

  • 1.6.0 双击标题栏放大窗口:我发现双击阅读器顶部标题栏没反应,可一旦加上双击缩放,又怕误伤顶部的工具栏按钮。到底怎么精准区分用户点的是空白处还是按钮? AI 写了两次独立的探针程序去测苹果系统的真实反应。结果探针一跑才发现,SwiftUI 的工具栏按钮在底层根本不是我们以为的常规控件!摸清了这层底细,AI自己找到方法解决了问题。
  • 1.6.1 笔记卡片中文输入法“吞字”:我在“分享笔记卡片”里用中文输入法快打中文词组时,字会莫名其妙消失,慢点打却没事。主代理查了代码,怀疑是“输入框一边接收我打的字,界面另一边又在更新,两边互相干扰,把还没选好字的拼音冲掉了。”我说:“先别改,用探针复现出来。”结果独立探针一跑,铁证如山:外部一刷新,正在组的拼音在 0.1 秒内就被清掉了,光标还跳到了末尾。抓到了真凶,AI换用能明确保护组字状态的原生输入组件,再经过探针复测和我自己的快打验收,把这个小毛病解决掉了。

你看,你完全不需要知道AI 最终怎么解决问题的,你只需要作为一个明白人告诉 AI:“别猜,先做实验。”


四、 让不同的AI“神仙打架”,但别当甩手掌柜

既然我自己看不懂代码,那就用魔法打败魔法——请不同的 AI 来给方案挑刺。

到了开发后期,我逐渐固定了一套“立项会审流程”:我用大白话提出一个需求,主代理写出立项草案 v1(里面把需要我拍板的选择题列成 D1、D2、D3……)后,我先不批,而是把草案扔给其他AI大模型(有时是 Astra 和 GLM 并行会审,有时是 GLM 评完 v1、再请 Gemini 评 v2)做独立挑刺。

主代理Claude Code 拿着其他大模型返回的评审意见——绝不整份照收,也绝不整份丢弃——与源码逐条核对,把真问题吸纳进修订稿,把假问题附上源码证据驳回,最后把干净的选择题并且带上它的推荐与分析摆在我面前,请我拍板。

这套机制帮我在方案阶段就消除隐患,尽可能地减少代码实现层面的返工。


五、 管住 AI 的“投机取巧”

跟 AI 打交道久了,你会发现它有时候像极了急于交差的下属:为了让你看到绿色的 PASS(测试通过),它会走捷径。

有些内部状态在测试里不好模拟,个别测试脚本为了方便注入条件,居然先用文本替换去修改测试副本里的生产源码,然后再跑测试!虽然测试绿了,但这留下了一个致命的疑问:你测过的那个东西,和我最终打包发给用户的东西,究竟是不是同一个东西?

从 1.1.5 到 1.1.7,产生了一条铁律(项目里叫 P7 门禁),还配了专门的审计脚本,用文件数字指纹(SHA-256)把文件锁死:严禁任何测试工具通过正则或文本替换去篡改生产代码。 同时,在修复杂 Bug 时(比如 1.5.8 打开图书中途突然关窗口的异常),我要求 AI 必须“先证伪,再修复”——先让新测试在没改的旧代码上跑出预期的 FAIL,证明这把尺子真的能抓出问题,然后再写修复代码让它变绿。

网上有一种意见,说Vibe Coding时代,没有必要写测试了。在我看来,说这话的人只是要一个玩具,他并不打算将它交付给最终用户。
我认为:恰恰因为我不逐行检查代码,才更需要测试。AI 说“改好了”,总得有个办法验一验。脚本负责模拟那些平时不容易碰到的异常,我负责亲自打开软件,检查它到底好不好用。


六、 克制自己的“清账强迫症”

开发过程中,我们一直留着一本“残余账本”:每做完一个版本,就把还没解决的问题、没来得及验证的情况,以及暂时决定不做的事情记下来。

这几类事情不能混为一谈。一个偶尔出现的小问题、一种还没测过的系统环境、一项明确不支持的功能,性质完全不同。账本里有一百条记录,不代表软件有一百个 Bug。 记下来是为了不遗忘,也提醒我们:没测过的不能说通过了,暂时接受的问题不能说已经解决了。

到了 1.5.8 前后,我日常用着已经挺稳,就让 AI 做了一次全项目代码体检,再把过去的记录集中梳理一遍。120 条旧记录,加上 300 多条后续线索,去重、拆分、分类后,最终整理成了 211 个条目。其中既有已经解决的事情,也有仍需观察的问题和风险。

连这份账本本身都出过错:第一版有遗漏和分类错误,经过外部复核才订正。可见,AI 整理出来的“问题清单”,也需要核实。

看着这份长长的清单,我的第一反应是:干脆全部清掉。但挑了几项、做过两轮整治后,我发现,投入了不少时间,日常使用的改善却很难感觉到。

于是,11 月 1 日,我给项目定下一条原则:

如果实际使用中没有不良反馈,也没有新的危害证据,就不要为了清空账本而主动修改。涉及数据丢失、安全或打不开书的可靠线索,则要单独评估,再决定是否修复。

后来的 1.6.2,就是一次这样的取舍。

账本里有条编号为 R048 的记录:如果保存阅读进度的文件损坏,软件会因为读不出进度,连书也打不开。更麻烦的是,“删除阅读记录”也要先读这个文件,所以连重置都做不了。

阅读进度出了问题,不应该连书都不让读。这触碰了阅读器的底线,我决定单独立项修复。

修复后的处理很简单:遇到损坏的进度文件,先把它改名保留,再从默认位置打开书。旧文件留下来供排查,用户可以继续阅读。

这次之后,我更倾向于把账本当作做决策的依据:目标是让产品更可靠,而不是让清单变成零。


七、 每次进步一个小数点:为什么一个小间距也要单独发个 1.6.3?

我的文档目录里,从 1.0.1 一路排到 1.6.3 ,一共有 70 个版本文件夹(其中很多是不对外发布的内部迭代版)。
你不禁会问:至于切得这么碎吗?
太至于了。不写代码的人玩 Vibe Coding,保命的秘诀就是“一次只做一件事,做好立刻封版”。

新手最容易犯的错,是在一个对话里塞一箩筐需求:“把标题栏颜色改了,顺便加个搜索历史,再把底部那个提示框往上挪一点。”结果一旦崩了,你根本不知道是哪句话惹的祸。

看看 1.6.3 是怎么诞生的:
当时我刚在真机上验收完 1.6.2 的存档自救功能,眼睛一瞥,发现底部弹出的信息提示胶囊,下沿被页码条给压住了一点点(这是从 1.1.9 版本就留下的老布局问题)。
我对 AI 说:“你顺便记录一下,这个提示胶囊显示的时候还要往上移,否则被页码区域压到了。”

接下来的流程是:

  1. 我拍板:“另开小版本处理。”先把 1.6.2 干干净净地封版、合并、打上标签;
  2. 专门为这个视觉细节立项 1.6.3,经过评审确认只动 ReaderScreen.swift 一个文件;
  3. 执行代理上去,新增 16 行、删除 2 行代码:当页码条出现时,把提示胶囊的底部基准从 16 pt 抬高到 40 pt,与旁边的返回按钮共用同一个定位常量;
  4. 跑完编译和门禁,我打开专用测试书,肉眼逐项确认 M1 到 M5 五个场景全部清爽漂亮,随后正式封版、打出公证包与商店包,发布 1.6.3。

小步快跑、独立封版,意味着每一步的改动范围都小到完全可控;哪怕出了岔子,我也能立刻找到回退点,把排查范围锁死在十几行代码之内。


写在最后

Silverlinger 从 1.0.0 开始到 1.6.3,我没有写过一行代码,但不代表我没有进行思考。

我想清楚了我的产品要什么,我想清楚了人机协同的规则,我在每一轮人机对话中权衡利弊做出裁判,我在真机验收环节上做最后的把关。

Vibe Coding 从一开始,拼的就不是谁的提示词写得无可挑剔,它拼的一直是你能不能把产品经理的常识、克制与证据意识,变成一套管得住 AI 的工程秩序。