先说结论
用 WorkBuddy 这类 AI 搭子做应用,"写代码"是最不值钱的环节。真正决定成败的是三件事:把场景想清楚、把验证做到真机、把发布闭成闭环。代码只是这三件事的副产品。
这篇文章不是教程,是复盘。素材来自一个真实项目——高中智能错题本 App「错题智库」从 v1.0 迭代到 v1.13.5(versionCode 28)的全过程,外加成长中心自动化、Supabase 边缘函数部署、学生端 UI 评审几个真实场景。16 条踩坑记录原样保留,因为坑比经验更值得记住——经验告诉你"什么能做",坑告诉你"什么会炸"。
① 场景选择 | 定输入输出、圈能力边界 | 把"AI 能做的"当成"我该做的" | 先摸边界,再定范围 |
② 文档准备 | 写需求 / 架构 / 验收标准 | 需求只活在脑子里 | 写成陌生人也看得懂的施工图 |
③ 开发 | 先骨架后血肉 | 平台差异没提前吃掉 | Web 先跑通,再套原生壳 |
④ 反复调试 | 定位真根因 | 把现象当根因、在错的环境复现 | 让真机自己记录现场 |
⑤ 上线发布 | 版本 / 缓存 / 调用闭环 | 发布成功 ≠ 用户拿到 | 版本号 + cache-busting + 指纹校验 |
⑥ 环境与协作 | 依赖、版本、节奏 | 环境差异 + 协作错位 | 固定版本、先评审后执行 |
下面按这六步展开。第 ④ 步是重灾区,全文最长的篇幅留给了它。
结论:选场景的第一原则是"先摸边界,再圈范围"。
AI 搭子最大的迷惑性在于——你说什么它都能做。于是很容易滑向功能膨胀,最后做出一个"什么都沾一点、什么都不闭环"的东西。这不是能力问题,是自律问题。
真实教训来自「成长中心自动化」。最初的设想很朴素:让脚本把成长中心的任务全领一遍。真跑起来才发现,能力边界极其清楚:
可自动化 | 盲盒、签到、兑换 | 能 |
不可自动化 | 发文、互动、真实浏览类任务 | 不能(要求真实外部行为,代刷既不合规也不可行) |
如果这个边界是在"开发到一半"才发现的,那就是纯粹的浪费。 好在它是在"先探一遍"的时候发现的——探一遍的成本,远低于做一半再推翻。
所以选场景时,先回答三个问题:
谁用? 用户画像决定取舍。错题本的用户是高中生,所以"按住说话"这种输入方式必须在真机上跑通——它不是锦上添花,是刚需。
输入输出是什么? 能画出一条完整链路的需求才叫需求。错题本的链路是:拍照/识题 → OCR 与 AI 解题 → 错题入库 → 生成复习计划。闭环清晰,每一步都有明确产物。
怎么证明它成了? 验收标准要前置。比如成长中心自动化的验证标准是"真开盲盒才算跑通",而不是"连接测试通过"。
一个判断小技巧:如果你写不出"怎么验证它是对的",那这个功能现在还不该进排期。 它不是需求,是愿望。
结论:AI 读的是文档,不是你的脑子。
很多人抱怨"AI 不懂我",翻译过来通常是"你没写下来"。文档准备这一步的产出质量,直接决定后面三个阶段要返工几次。
本项目能一路迭代到 v1.13.5,靠的是一份约 71 KB、7 章、16 条踩坑的交接文档《工作交接-错题智库 v1.13.5》。它做对了三件事:
写清 What:功能清单、页面结构、数据结构、接口约定,一项不落。
写清 How to verify:每个能力都写"怎么算成功"。测语音功能,写的是"真机能出文字",不是"按钮颜色正常"。
写清 Constraints:平台只支持 Android、localStorage 单机持久化、模型锁死某个版本……边界写死,AI 才不会替你"发挥"。
除此之外,还有一类东西常被忽略,但必须写进文档——协作协议。比如"变更前先给方案、确认后再落地"、"部署类任务要一次给到最终 URL"。这些不是需求,是你和搭子之间的工作约定。写进去,后面能省掉大量来回确认。
通用经验补充:给 AI 写需求,最有效的结构是「功能清单 + 数据结构 + 验收标准 + 已知约束」四段式。缺任何一段,都会在后期以"返工"的形式补回来,而且是带利息的。
还有个小技巧:文档里最好带一页速览表。七章文档没人想看第二遍,但那张表会被反复引用。信息密度高,比篇幅长重要得多。
结论:开发阶段最贵的不是写功能,是吃掉平台差异。
错题智库的架构很典型,也很值得抄:
浏览器 / Android WebView
└─ index.html(单页应用,hash 路由)
├─ js/data.js 演示数据与数据模型
├─ js/utils.js 存储层 / SM-2 间隔重复 / 掌握度模型 / LaTeX 混排
├─ js/charts.js 零依赖 SVG 图表
├─ js/ocr.js OCR 封装(Tesseract.js v5 + PDF.js)
├─ js/ai.js AI 总控:LLM / TTS / ASR / 苏格拉底引导(79 KB)
├─ js/app.js 路由 / 交互 / 按住说话状态机(186 KB)
└─ js/pdf-export.js + 七处导出
Android 桥
├─ MainActivity.java 权限申请、JS 桥注册
├─ FileChooserClient.java 文件选择 + WebView 权限桥
├─ AsrBridge.java 原生语音识别
├─ TtsBridge.java 原生 TTS
├─ PickerBridge.java 图库/相机/文档原生直达
├─ DownloadBridge.java 下载
└─ AssetWebViewClient.java 资源加载"Web 优先,原生补差"这条路线有个巨大好处:功能能在浏览器里先跑通、先验证,等真的需要系统能力了,再一层层接原生——而不是一上来就陷进 Android 工程的泥潭里。
但代价也很明确:平台差异会在这一步集中爆发。
content:// 地址 fetch 失败 | 系统选择器拿到的文件,网页读不了 | 跨源 / 协议限制 | 原生做同源代理 |
密码框自动填充"只画界面不给值" | 界面显示有值,input.value 却是空 | 浏览器自动填充只渲染、不写值 | 三级回落:input.value → 已存配置 → 默认值 |
默认值与 input.value 分裂 | 用户没改动,默认值被当成空值存进去,静默抹掉原配置 | 默认值靠 value 属性回填,保存却只读 input.value | 试听/保存双处拦截,界面有值 ≠ input.value 有值 |
豆包通道 CORS | 请求被浏览器拦下 | 非 OpenAI 协议 | Key 走 URL 参数绕 CORS |
lambda 编不过 | -source 8 撞上 javac 20 | Java 版本兼容 | 改用匿名 Runnable,不用 lambda |
overlayfs 副本写不进去 | 七副本内容不一致 | overlayfs 必须先删后写 | 先 rm -f 再 dd,MD5 唯一性校验 |
这些坑有个共同点:它们都不是"逻辑错",而是"环境错"。 逻辑错,AI 自己往往能发现;环境错,只有把代码放到真实平台上跑才会暴露。
所以开发阶段的正确姿势是:越早把代码放到最终运行环境里跑,越早发现问题。 别等到"功能全做完了再上真机",那时候每修一个环境坑,都要连带重测一堆功能。
这一节写长一点,因为它是整个项目里最贵的学费。
错题本里有个"按住说话"的语音作答按钮。真机上,按住之后按钮永久变红,松手也不提交。用户一测就卡,开发者一测就"没问题"。
从 v1.13.2 到 v1.13.5,连续四个版本都在修同一个"卡红",每次都觉得找到根因了,用户一测——还是卡。
v1.13.2 | 第一次卡红 | partial 中间结果提前把状态切成"识别中",松手没触发提交 | 全程保持红、松手必提交、引擎 4s 静默自动收尾;步骤按钮独立 |
v1.13.3 | 第二次卡红 | 引擎自行静默结束,stop() 变成空操作 | stop() 明确返回布尔并立即复原;新增 9s 页面看门狗 |
v1.13.4 | 仍不对,且明确要求"要真能出文字" | 降级链路顺序 / 复原延迟 | 降级链改云端优先;删掉 9s 看门狗改"松手立即复原"(实测 321ms);给"无系统识别 + 未配 Key"真实 UI 引导 |
v1.13.5 | 第四次卡红 | 终于找到真根因:WebView 双层权限不一致 | 修 Java 权限桥(挂起—补授权 + 3s 兜底 deny);APK 内置语音诊断页 |
Android WebView 里,getUserMedia 要过两道关:
网页层:WebChromeClient.onPermissionRequest
系统层:RECORD_AUDIO 运行时权限
旧代码无条件执行 request.grant(...),于是:
网页层被放行,getUserMedia 不抛 NotAllowedError;
但系统层其实没有权限;
结果 getUserMedia 返回的 Promise 既不 resolve 也不 reject——它悬在半空;
ASR.state 永远停在 'starting';
而 UI 把 starting 也算作 listening → 按钮永久红。
一个没有落定的 Promise,让整个状态机卡死在原地。
这是最值钱的一课:
仿真环境里,两层权限要么都给,要么都不给;不存在"网页层放行 + 系统层无权限"这个中间态。
所以前四次修复,等于在仿真环境里"盲修"。每次改完,仿真环境都告诉你"好了"——因为它从来就复现不出这个问题。
方法论教训:真机问题不要在仿真环境里复现。 v1.13.5 起改策略:让真机自己记录现场——APK 内置语音诊断页,包含环境快照、一键测麦克风、指针事件监视器、全程埋点、一键复制。不再指望"远程复现",而是把现场证据打包带回来。
① 现象 ≠ 根因,中间隔着一层"翻译"。
同一个项目里还有一组对照案例。跑成长中心自动领取脚本时,点击/探测频繁失败,第一反应是"选择器失效、目标元素丢了",改了半天选择器,没用。真实根因是 Chrome 沙箱进程树被回收——受限环境会杀掉浏览器进程,CDP 长连接随之断开。表象像选择器问题,根子在进程层。
对策:连接失效,先查进程与连接存活,再谈选择器。顺序不能反。
② 让失败"说人话",禁止"提示音冒充成功"。
早期版本里,TTS 失败时会播放一个兜底提示音,用户误以为"成功了只是没声"。真实的 401 Key 失效报错,被这声提示音盖住了。
对策:每一层失败都必须渲染明确原因。 TTS 按失败渲染完整原因链 + Key 对照提示;ASR 四级降级链每层失败都给真实可照做的 UI 引导。
16 条踩坑里,近半数源于同一类病根——假成功 / 静默失败。
③ 所有"可能永不落定"的异步,都必须有兜底。
getUserMedia 可能永不 resolve/reject。修法是加 3 秒兜底 deny:宁可明确失败,绝不悬空。
通用规则:一切等待都要有兜底答复,一切可能挂死的调用都要有超时。
④ 流式/增量结果:中间结果只更新内容,不更新阶段。
partial 结果提前切状态,就是 v1.13.2 的坑。
通用规则:语音识别、实时转写、渐进式搜索的 UI 状态机——阶段转移只能由明确的生命周期事件驱动,中间结果不许碰状态。
⑤ stop() / close() / cancel() 必须幂等且返回布尔。
v1.13.3 的坑:stop() 内部三层分支全靠内部状态判断,却没有兜底 else;引擎实际已结束、内部状态没同步时,所有分支都不命中,stop() 变成空操作。
通用规则:内部状态机不可信时,API 必须返回"这次调用到底做了没有",并具备幂等兜底路径。
⑥ 平台差异要"按平台验证",不能靠推理。
原生/云端两套独立音频采集,如果设计成"先 A 失败再补录 B",会录到静音。对策是按下即选通道,不做串行降级。
原文 16 条坑里,有 6 条在任何项目上都成立:
悬空 Promise 必须有兜底
stop() 必须返回布尔
界面有值 ≠ input.value 有值
部分结果不得提前切状态
CDN 边缘缓存需 cache-busting
overlayfs 多副本必须"先删后写"
结论:发布是一个"闭环动作",不是一次"上传动作"。
v1.10.0 和 v1.13.5 都撞上过同一个问题:APK 已经更新,下载页还是旧包;复核脚本读到的还是旧代码。查响应头,eo-cache-status: HIT——下载页的 index.html 裸 URL 被边缘缓存命中了。发布是真的发布了,但请求打到了旧缓存上。
发布三件套(缺一不可):
版本号参数:APK 直链带 ?v=<version>
主动 cache-busting:下载页 HTML 复核必须带 ?_=Date.now()
文件指纹校验:发布后核对 MD5,确认拿到的真的是新包
补充一条硬规则:发布流水线写七副本时,先 rm -f 再 dd(overlayfs 必须"先删后写"),并以 MD5 唯一性做最终校验。
通用规则:任何走 CDN / 边缘缓存的静态资源发布,都要过这三件套。缓存不会报错,它只会安静地骗你。
平台选的是 Supabase Edge Functions(Deno 运行时),方式明确走 Management API + 零依赖工具包(deploy.mjs / deploy.sh / secrets.sh),不走 CLI / Docker。
这条路线的好处在受限环境里特别明显:CLI 要装、Docker 要起、网络还要通,每一环都是新的失败点。用 Management API,把这些环境依赖全砍掉,把"部署"压缩成一个纯 HTTP 调用。
交付标准是"一次到位":部署完直接给可访问的 http URL,不留半截。
上线后的 video-search 函数遇到了一个典型问题:函数部署成功,日志正常,但 App 端一调就截图报"代理不可用"。
排查下来的高风险点集中在三类:
对端接口需要 Cookie / 鉴权态,请求里没带;
跨域或请求头处理缺失;
返回字段名与 App 期望不一致——别名没对齐。
修法是:函数内补 Cookie 处理 + 字段别名映射,让出参严格对齐 App 侧字段;然后用零依赖工具包重部署,绕开 CLI/Docker 环境差异。
最关键的一条:必须让真实调用方实测,才算闭环。 部署返回 200 只能证明"函数活着",不能证明"链路通了"。
复用场景:任何"第三方 API 代理类"边缘函数,必须处理对端鉴权态 + 字段对齐,且由真实调用方验证。
公益项目推荐流程里,生成过一次微信小程序授权码给用户扫——码过期了,授权链路没走通。解法很朴素:重新生成第二次,并在有效期内引导用户到小程序里点"同意"。
通用规则:所有临时凭证(小程序码、登录二维码、一次性 token)——生成后尽快引导用户操作,过期即重发,绝不复用旧码。
APK 直链带版本号 |
|
下载页复核 | 请求带 |
文件指纹 | MD5 与本地包一致 |
后端端点 | 返回 200 且调用方实测通过 |
多副本一致性 | 七副本 MD5 唯一且一致 |
真机验收 | 关键链路(如语音)真机跑通 |
结论:代码之外的坑,往往比代码里的更贵。
git clone 开源仓库一直 443 失败(GitHub 直连被墙)。反复重试没用,改用镜像 zip 下载——拉仓库 zip 包、本地解压、落到技能目录,再走安全审查。
通用规则:受限网络环境拉第三方代码或依赖,第一备选永远是镜像源,而不是反复重试直连。
本地 Windows 跑得好好的脚本,换到另一个环境行为就不一样。根因是运行时版本差——本地 Node 22.22.2-3,目标环境 Node 22.13,小版本差也可能踩到 API / 行为差异。
通用规则:跨环境脚本避免依赖某个小版本特有的 API;部署类任务固定运行时版本,或在工具包里声明兼容范围。给别人的可执行脚本,标注兼容版本——避免"我这边能跑"。
装外部技能前,必跑完整审查:
获取:直连失败 → 镜像 zip 下载解压
审查:逐个过 SKILL.md + 全部脚本 + 配置文件
扫描:危险模式扫描——网络请求、subprocess、eval、非标准库依赖
确认 + 安装:把审查结论交给人确认,通过后才落到技能目录
实测一份第三方技能包:4 个 SKILL.md + 5 个 Python 脚本 + 1 个 openai.yaml,全部干净(仅用标准库,无网络、无子进程、无 eval)——这个结论不是"看着像安全",是逐项扫出来的。
多次任务里,用户反复要求"先给建议/评审,确认后再执行",UI 评审还要高保真参考样例对照。
这不是 bug,是协作偏好:变更前先看结果、确认后再落地,避免返工和信息错位。
对策:养成双阶段节奏——① 出建议 / 评审 / 参考样例(不落地)→ ② 等确认 → 再执行。而部署类任务反过来,追求一次到位交付(直接给可访问 URL)。这套节奏本身就能减少反复调试的总量。
整个项目沉淀下来的做法里有两条特别值得抄:
置信度标注:每条结论标 ★确证(有直接来源)/ ☆推断(合理归纳但待核实)。含糊比错误更贵,标注清楚反而更快。
未闭环事项清单:把"待真机验证""字段对齐待观察""授权后中断待继续"逐条列出来,标明下一步。没做完的事被写下来,才不会假装做完了。
场景选择 | 能力边界后知后觉 | 把"能做"当"该做" | 先探边界,再圈范围 |
文档准备 | 反复返工 | 需求没落成文字 | 四段式:功能 + 结构 + 验收 + 约束 |
开发 | 环境错当成逻辑错 | 平台差异后置暴露 | Web 先跑通,越早上真机越好 |
调试 | 连接"元素丢失" | 其实是进程树被回收 | 先查进程存活,再改选择器 |
调试 | 真机"卡红"连修 4 版 | 仿真环境复现不出中间态 | 让真机自己记录现场 |
调试 | 状态机卡死 | 悬空 Promise 无兜底 | 一切等待必有兜底答复 |
调试 | 失败被误认为成功 | 提示音掩盖了真错误 | 禁止假成功,失败给完整原因链 |
调试 | stop() 变空操作 | 分支无兜底 else | 停止型 API 返回布尔 + 幂等 |
调试 | 按钮状态错乱 | partial 提前切状态 | 中间结果只更新内容,不更新阶段 |
开发 | 配置被静默抹掉 | 界面有值 ≠ input.value 有值 | 取值三级回落 + 双处拦截 |
发布 | "已发布"还是旧版 | CDN 边缘缓存 HIT | 版本号 + cache-busting + 指纹 |
发布 | App 报"代理不可用" | 部署成功 ≠ 链路通 | Cookie/字段对齐 + 调用方实测 |
发布 | 授权码扫了没反应 | 临时凭证过期 | 过期即重发,不复用 |
环境 | clone 一直 443 | 直连被墙 | 第一备选是镜像源 |
环境 | 跨环境行为不一致 | 运行时小版本差 | 固定版本 + 标注兼容范围 |
协作 | 反复返工 | 直接落地未经确认 | 先评审后执行,双阶段节奏 |
心法一:先想清楚,再动手。
场景选择的成本是 1,开发返工的成本是 10,上线后返工的成本是 100。把时间花在前面。
心法二:把"怎么验证"写进需求里。
写不出验收标准的需求,都是愿望。验收标准前置,是唯一能对抗"自我感觉良好"的东西。
心法三:越真实的运行环境,越早介入。
仿真环境只能验证"理想态"。真机的中间态、沙箱的进程回收、CDN 的边缘缓存——这些坑只在真实环境里存在,也只在真实环境里能被解决。
心法四:让失败说话,别让它沉默。
假成功比失败更危险。一个消音的错误,会让排查成本翻十倍。宁可明确失败,绝不悬空、不掩盖、不糊弄。
心法五:闭环才算完成。
部署成 200 不算完成,调用方实测通过才算;文件传上去不算完成,用户拿到新版才算;文档写完不算完成,落成可复用的资产才算。
用 WorkBuddy 搭应用,它替你写代码,但替不了你思考。
AI 能把"写"这件事的成本压到接近零,于是"想"和"验"的价值就凸显出来了——代码越廉价,判断力越贵。
那些坑不是障碍,是路径。踩过、记下、写进文档,它们就从"学费"变成了"资产"。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。