事情是这样的。
我最开始只是想做一个情侣生活记录 App。
两个人可以在日历里记录纪念日、日程和生活琐事,再加一个共同账本,能记收入、支出、分类和备注。以后有时间,再补图片、月度总结、年度总结和两台手机同步。
需求大概就这么多。
没有原型,没有完整技术方案,没有 Android 工程,甚至连开发环境都没有完全配好。手边只有一台 Mac、一部华为手机,以及 Codex。
后来这个项目真的长了出来。
它有 Kotlin 和 Jetpack Compose 写的界面,有 Room v4 数据库,有日历、纪念日开屏、图片附件、共同记账、月度和年度总结,还有一套用 ECDH 与 AES-GCM 做加密的局域网同步。中间甚至折腾了一轮跨应用自动记账。
版本从最早的兼容性探针,一路走到了 0.9.2-category-management。
但坦率地讲,这篇文章不是一个「我不会编程,跟 AI 说句话就做出了 App」的爽文。
恰恰相反。
真正让我觉得值得写下来的,是那些没那么爽的部分。环境不兼容、数据库迁移、真机掉线、测试依赖冲突、支付页面一闪而过、自动化测试把调试包卸载了,连本地数据和无障碍授权一起清掉。。。这些事情才组成了 AI 编程的真实现场。
如果你也想用 Codex、Claude Code 或 Cursor 做一个完整项目,我希望这篇复盘能帮你少走一点弯路。
1. 不要从写功能开始,先证明设备和工具链能工作
项目一开始,我和 AI 讨论了几条技术路线。
Flutter 的优势是跨平台,React Native 适合已有前端经验的团队,原生 Android 则能更直接地处理通知、相册、后台任务、无障碍和华为设备兼容。
因为目标设备都是能安装 APK 的 HarmonyOS 4.x 华为手机,最终选择了 Kotlin、Jetpack Compose 和 Room。
这时最容易犯的错,是马上让 AI 创建日历页面和记账页面。看起来进展飞快,实际上最基础的问题一个都没回答。
这台手机到底对应哪个 Android API?
Mac 上的 JDK 能不能跑现代 Android Gradle Plugin?
ADB 是否已经授权?Room 能否在真机写入?Compose 是否能正常渲染?
所以第一版 App 什么业务都没有,只是一个兼容性探针。
它做了几件非常无聊,但后来证明非常值钱的事。
设备确认是华为 ALN-AL00,HarmonyOS 4.2,底层兼容 Android 12,也就是 API 31。Mac 终端里的 Java 11 不够用,于是构建改用 Android Studio 自带的 JDK 17。ADB 一开始显示 unauthorized,在手机上确认授权后才变成可用状态。
探针 APK 安装后,只验证冷启动、Room 写入读取、网络状态、本地通知和崩溃日志。
全都通过以后,才正式进入业务开发。
我后来越来越觉得,AI 编程项目的第一步不是做 MVP,而是做「环境 MVP」。
今天就能执行的动作也很简单。不要先做首页,先做一个最小探针,证明下面这条链路完整跑通。
代码生成
↓
本地编译
↓
自动测试
↓
生成安装包
↓
目标设备安装
↓
启动与日志检查
如果这条链路不通,后面写得越多,返工越大。
2. 模糊需求不能直接进代码,要先变成可验收的阶段
Love Space 最初的需求听起来很明确,日历、记账、图片、总结、双端同步。
但真要落地,每个词下面都是一堆分叉。
日历要不要农历?要不要提醒?纪念日怎样重复?记账金额用浮点数还是整数分?图片放数据库还是文件系统?删除是物理删除还是软删除?个人账本是否同步给伴侣?两台设备不在同一 Wi-Fi 怎么办?
如果把这些问题一次性全塞给 AI,它通常会给出一个看起来面面俱到的方案。问题是,面面俱到往往也代表没有真正做取舍。
我们的做法是每次只锁定一层。
阶段 1 先做公历、每年重复和稳定的手工记账。农历不做,提醒暂时不做,自动记账也不做。
阶段 2 再做图片、月度总结和年度总结。
阶段 3 才讨论两台设备同步。
每轮讨论里,AI 会把真正改变架构的歧义挑出来。我只需要做产品判断。
有一次我想在共同账本增加「全部、我、他人」筛选。AI 没有马上改界面,而是先问,这里的「我」到底指谁创建了账单,还是谁实际付款。
两个概念看起来很像,数据模型完全不同。
最后我们确认按 createdByMemberId,也就是谁创建了这笔账单来筛选。这样不需要数据库迁移,列表、收入、支出、结余、分类统计和月度年度总结都能复用同一个查询边界。
这个来回特别典型。
人最应该保留的工作,不是逐行告诉 AI 怎么写 Kotlin,而是决定产品语义。AI 可以把一个决定扩散到 DAO、Repository、ViewModel 和 Compose 页面,但它不能替你决定「创建者」和「付款人」哪个才符合真实需求。
后来我把需求表达固定成了四块。
目标
用户最终要得到什么
范围
这轮明确做什么,不做什么
约束
设备、数据兼容、权限、隐私和不能破坏的旧行为
验收
用什么测试、页面和真实操作证明它完成了
一个实际使用过的提问方式是这样的。
我想在共同账本增加全部、我、伴侣三个筛选。
筛选要影响账单列表、收入、支出、结余、分类统计和月度年度总结。
先讨论它应该放在哪一层,以及是否需要数据库迁移,本轮不要改代码。
这比「帮我加一个筛选」有效太多。
3. AI 写代码之前,先让它写设计和实施计划
项目做到后面,仓库里留下了 19 份设计或实施文档。
它们覆盖兼容性探针、阶段 1、阶段 2、局域网同步、日历视觉重构、纪念日开屏、账单图片、自动记账和分类简化。
我一开始也觉得这可能有点重。
一个个人项目,真有必要写这么多计划吗?
后来发生过几次会话中断和上下文压缩,答案就很明确了。有必要。
聊天记录再长,也不是可靠的工程状态。真正能让 AI 在几天后接着工作的,是项目里的设计决定、数据边界、待办步骤和验证命令。
每个复杂功能开始前,AI 会先生成两类文档。
设计文档负责解释为什么这样做。实施计划负责写清要改哪些文件、先写什么测试、跑什么命令、预期看到什么结果。
以局域网同步为例,设计阶段先确定了几件事。
- 数据仍以本机 Room 为唯一事实来源
- 同步只在双方主动打开 App 时运行
- 两台设备通过本地身份和配对码建立关系
- 使用 ECDH 派生会话密钥,再用 AES-256-GCM 加密
- 普通单端修改自动合并,双方同时修改才进入冲突处理
- 图片只传对方缺少的文件
等这些决策稳定后,实施才拆成数据库 v3、身份与加密、快照编解码、NSD 发现、双向传输、创建者角标、冲突页面和最终验证。
这里真正有用的不是文档数量,而是「把讨论结果写回仓库」。
AI 的记忆可以断,工程的记忆不能断。
4. 数据库迁移是红线,不能用能启动来冒充没丢数据
Love Space 的数据库最终从 v1 走到了 v4。
v1 是日历、账本、分类、标签和账单这些核心业务。
v2 增加日历图片元数据与月度年度聚合。
v3 增加成员身份、创建者、最后编辑者、同步基准和冲突数据。
v4 增加账单单图附件。
每次升级都保留显式 Room Migration,并用独立迁移测试构造旧版本数据,再升级到新版本检查原记录和新增字段。
这里有一个很容易被忽略的坑。
App 升级后能启动,不代表数据迁移正确。数据库可能已经被重建,也可能某个字段被静默置空。用户打开首页看到界面正常,过两天才发现旧账单没了,那时已经晚了。
所以迁移测试至少要回答三个问题。
旧数据还在不在?
新表和新字段是否符合预期?
现有外键、索引和关联是否仍能查询?
图片架构也在这里做了一个关键取舍。图片不直接塞进 Room,也不长期保存相册 URI,而是复制到应用私有目录,数据库只保存文件元数据。
新增记录尚未保存时,图片先放临时目录。保存成功才转为正式附件。取消编辑就清理临时文件,删除记录才清理正式图片。
这个流程听着比「选完图片保存路径」麻烦,但它解决了几个真实问题。相册原图被移动后不会失效,数据库不会因为大块二进制迅速膨胀,用户取消编辑也不会误删原附件。
如果你的 AI 编程项目涉及本地数据,我建议从第一天就写下这条规则。
任何 schema 变化都必须有显式迁移和旧数据测试。正式版本不允许用破坏性重建解决编译错误。
这句话很朴素,但它保护的是用户真正拥有的东西。
5. 把测试分层,不要只问 AI「测试通过了吗」
Love Space 后来形成了三层验证。
第一层是纯 JVM 单元测试,速度快,覆盖金额规则、年度重复、纪念日天数、分类目录、统计周期、创建者筛选、同步编解码、加密往返、合并策略、自动记账页面解析和队列冷却。
第二层是 Android 仪器测试,主要验证 Room v1 到 v4 的真实迁移,以及依赖 Android 数据库环境的查询。
第三层是真机验收,检查华为系统权限、冷启动、相机、相册、Compose 布局、无障碍服务、第三方支付页面和 ADB 安装。
每一层解决的问题不同。
单元测试可以证明金额解析器没有把优惠金额当成实付款,但它证明不了淘宝某个版本的支付成功页会停留多久。
迁移测试可以证明旧表升级后数据还在,但它证明不了分类卡片在华为手机上有没有排成一列。
真机截图能证明界面看起来没问题,但它证明不了月度结余是否受列表加载上限影响。
所以完整的交付命令通常是这样的。
./gradlew testDebugUnitTest lintDebug assembleDebug
然后校验 APK 签名和 SHA-256,再通过覆盖安装保留手机数据。
adb devices -l
adb install -r app/build/outputs/apk/debug/app-debug.apk
adb shell am start -n com.wenfarong.lovespace/.MainActivity
安装后还要核对包内版本、进程状态和 AndroidRuntime 崩溃日志。
到 0.9.2 时,项目一次完整回归包含 55 项单元测试、Android Lint、Debug APK 构建和 v2 签名检查。
测试不应该是一句总结。
它应该是一串可以重新运行的证据。
6. 真机是第四个参与者,它专门负责打脸
如果只看代码和模拟测试,自动记账一度显得已经完成了。
第一版会监听支付宝、微信、淘宝等支付应用的无障碍页面,识别成功状态、金额和商户,再弹出 Love Space 确认面板。用户确认后才写入账本,不做自动点击,也不上传页面原文。
测试里,支付宝支出、微信收款、多金额页面、退款、失败页和非白名单应用都通过了。
然后拿到真机上,微信一元转账没有触发。
淘宝支付也没有触发。
不是哥们,单元测试不是都绿了吗???
ADB 检查后发现,无障碍权限已经开启,服务也处于绑定状态。问题不在系统权限,而在页面生命周期。
淘宝支付成功弹层停留时间很短,随后自动返回购物车。旧实现等 450 毫秒再读取当前页面,并且要求用户仍停留在原支付应用。等它真正开始识别,成功页已经没了。
修复后的版本改成事件到达时立即抓取页面快照,监听间隔降到 80 毫秒,并允许支付应用退到后台后继续显示确认面板。金额规则也补上了「实付款 3.90」这种没有人民币符号的格式。
后来为了应对更多瞬时页面,又加入最多 8 个快照的有界队列、900 毫秒同页冷却、事件触发与 0.8 秒定时兼容模式,以及微信详情页的有限重试。
可即便如此,最新诊断仍然发现规则过于依赖固定页面类名。微信聊天页为了避免把历史转账卡片误记,被主动排除。淘宝更新后使用了更多 Activity,原来的两个页面白名单又不够了。
所以这项功能目前只能诚实地标为原型。
这一段经历让我重新理解了「AI 已经实现」。
它最多代表代码链路存在、测试样例通过。只有真实设备、真实系统和真实第三方应用一起跑过,功能才有资格谈稳定。
7. 最大的一次事故,不是代码 bug,而是测试策略错了
项目里最让我后怕的一次事情,发生在创建者筛选功能完成之后。
为了验证数据库和界面,执行了一轮 connected Android 仪器测试。测试流程结束时,Gradle 卸载了临时安装的调试包。
结果是什么呢?
本机原有的 Love Space 数据没了,配对身份没了,无障碍授权也被系统清除了。
代码测试都通过了。
用户数据也真的没了。
这一下给我彻底整清醒了。
自动化测试本身也有副作用。尤其是移动端,安装、卸载、清数据、测试 runner 和包签名都会改变设备状态。不能因为命令名字里有 test,就默认它是安全的。
从那以后,管线增加了几条硬规则。
- 保存正式数据的手机不再随意运行完整 connected 测试
- 仪器测试使用模拟器、备用机或独立测试数据库
- 日常安装只使用
adb install -r - 安装前后核对账单数、日历数和本机身份摘要
- UI 验收只打开页面,不创建或删除正式记录
- 任何可能卸载应用或清数据的命令都要提前说明
你如果正在用 AI 操作真机、数据库或线上环境,最好也给任务补一句。
这台设备包含需要保留的数据。
禁止卸载应用、清除数据或运行可能重装测试包的完整仪器测试。
只允许覆盖安装,并在操作前后核对数据数量。
这不是多余的提示词。
这是生产边界。
8. AI 最适合做扩散,人必须负责收敛
回看整个项目,AI 做得最好的事情是扩散。
一个产品决定确定后,它能迅速找出涉及的数据库字段、DAO 查询、Repository、ViewModel、Compose 页面、测试和发布版本。它也能把一个失败现象拆成权限层、事件入口、规则匹配、金额解析和浮层显示几个阶段,然后逐层排查。
但项目里最重要的几次收敛,都来自人的判断。
日历提醒先不做。
标签不再作为核心交互,但旧数据和同步字段必须保留。
分类删除不能直接物理删除,要把关联账单转入「其他」。
所有情侣空间数据最终统一共享,不再维护个人与共同两套语义。
自动记账不能因为用户说「全自动」就静默写入,第一版仍要弹出确认,避免误记。
这也是我现在认为比较舒服的分工。
人负责目标、优先级、产品语义、权限边界和最终验收。
AI 负责调查代码、提出方案、写实施计划、修改多文件、运行测试、整理证据和记录失败。
真机负责指出两边都没想到的问题。
三者缺一个,项目都不太稳。
9. 一套可以直接复用的 AI 编程闭环
如果让我重新从零再做一次,我会把整个流程收敛成下面这条管线。
模糊想法
↓
目标、范围、约束、验收标准
↓
兼容性探针
↓
设计文档与技术决策
↓
实施计划与失败测试
↓
最小实现
↓
定向测试
↓
全量单测、迁移、Lint、构建
↓
签名与产物校验
↓
覆盖安装
↓
真机只读验收
↓
用户真实场景测试
↓
失败记录写回项目
每一轮都只处理一个主要目标。
不要一边做数据库迁移,一边顺手重构 UI,再顺便升级依赖。AI 很容易在长任务里不断扩大范围,最后每一行改动都看起来合理,却很难回答哪一行对应原需求。
任务开始时,可以直接使用这份简报。
目标
写清用户最终要得到的结果
现状
说明当前版本、相关模块和已知问题
范围
列出本轮必须完成的行为
不做
明确排除相邻功能和重构
数据边界
说明哪些旧数据、协议和权限不能破坏
验收
列出测试命令、真机页面和真实操作
交付
要求给出改动文件、测试结果、APK 版本和已知限制
实现过程中,也不要只问「做完了吗」。
我更常用的追问是这些。
这个结论有什么代码或日志证据?
哪些部分只通过了模拟测试,哪些已经真机验证?
这次是否修改数据库结构,旧版本如何迁移?
有没有运行会卸载应用或清数据的测试?
还有哪些功能只是实现了规则,但没有真实平台验证?
AI 很擅长给出完整感。
而这些问题,会把完整感重新拆成证据。
10. 如果现在重来,我会更早做的五件事
第一件事是第一天就初始化 Git。
Love Space 最开始不是 Git 仓库,虽然计划文档保存了大量工程记忆,但缺少提交检查点、版本标签和可靠回滚。后续再做同类项目,我会让每个可安装版本都绑定提交 SHA、APK 哈希和迁移版本。
第二件事是把模拟器、测试机和日常数据机分开。
不会再让 connected 测试碰保存正式数据的手机。
第三件事是给复杂识别功能先做诊断模式。
自动记账最初没有记录页面类名、事件类型和失败阶段,导致真机不触发时只能不断猜关键词。更合理的顺序应该是先做隐私受控的本地诊断,再根据真实页面建立规则。
第四件事是为功能状态建立等级。
已写代码
已通过单元测试
已通过真机单次验证
已覆盖多个应用版本
稳定支持
只写「支持微信、淘宝」太容易让人误解。自动记账这种依赖第三方页面的功能,更应该明确支持等级和核验版本。
第五件事是更早冻结数据模型。
v1 到 v4 的迁移都成功了,但频繁变化会不断放大同步、附件和测试成本。核心体验稳定后,应该先发布一个数据结构冻结版,再考虑远程同步和更多自动化入口。
写在最后
这个项目让我最意外的,不是 AI 能写多少代码。
代码当然写了很多。从 Compose 页面到 Room DAO,从同步协议到无障碍服务,它确实把一个人能推进的工程范围拉大了。
但真正决定项目能不能活下来的,是另一套没那么显眼的东西。
把模糊想法变成明确边界。
把聊天结论写回仓库。
把「已经完成」拆成测试、迁移、构建、安装和真实使用。
把每一次失败变成下一轮的约束。
我以前会把 AI 编程理解成「让 AI 写代码」。现在我更愿意把它理解成「和 AI 一起经营一条证据链」。
需求是证据,设计是证据,测试是证据,APK 是证据,真机上的失败也是证据。
最后你得到的不只是一个能打开的 App,而是一套即使换一个模型、换一个会话、换一个开发者,也能继续往前走的工程过程。
Love Space 还没有真正完成。局域网同步仍缺两台真机的完整验收,自动记账也还在原型阶段,Release 签名和正式发布流程同样需要补齐。
说实话,我们还差得远。
但从一段需求,到一个可以装进手机、被真实使用、被真实问题反复锤过的 Android App,这条路已经走通了。
而这条路,比某一次生成出来的代码重要得多。