TL;DR :这是一个 Android 团队让 AI 自己跑代码、自己抓 bug 的实践。本文从一个真实痛点切入:AI 写完代码、单测全过,但只要装到真机上立刻翻车。我们试过让 AI"写更好的代码",发现这条路有上限------bug 的本质是只在运行时才能被发现。最后做了一套让 AI 自己运行 + 自己验证 + 自己修的三层闭环,验证之后 reviewer 终于不用花精力在"它能不能启动"上。
故事的起点:一行 errno=40001
过去一年,作为一名移动端研发,笔者一直在尝试与AI协作一起写代码,但是跟服务和架构都拆得比较细的服务端开发相比,移动端开发的特点是:1、UI与逻辑并重,AI还原和测试UI的能力较弱。2、无论你让AI如何写更完整的单测,整个App集成运行时还是有各种各样的bug,移动端程序面临真机运行环境和后端服务环境和异常数据等多重考验代码的健壮性。
三个月前的一个下午,我让 AI 自己实现了一个新闻列表 + 加载更多 + 下拉刷新。代码、单测、lint 都过。
我装到真机上,一启动就是这一行:
ini
errno=40001 user_id header is required
人来调试发现:服务端的 API 文档说"cookie 自动派生 user_id",但实际只认一个显式的 HTTP header。AI 没读出这个差异------它只信文档。
类似的事情隔三岔五就遇到一次:
- 文档 说
score是0~1的 float,服务端实际返回99(int 0..100) - 某个 BaseActivity 子类要求根 view 必须有特定 id,没文档说明,漏配就启动崩溃
- Figma 标题用衬线字体,AI 实现用了 Compose 默认无衬线------单测看不出来
- 服务端图片是
.svg格式,主流图片库默认不支持------加载失败但不报错
每一次都是:单测全过、编译全绿、装到真机或者眼睛看一眼立刻翻车。
一、难点:让 AI 写更好的代码这条路走不通
直觉上的解法可能是:让 AI 写代码更仔细一点。再加一些单测。再 prompt 它"注意文档偏离的可能性"。
试了一阵发现,这条路有上限。
因为这些 bug 的本质是------它们只在运行时才能被发现。不管 AI 推理多深、单测覆盖多广,只要它没真把 app 装上设备、没看一眼 logcat、没截一张图跟设计稿对一下,这些坑就抓不住。
举上面那个 user_id header 例子:是下划线还是横线,API 文档自己都模糊。AI 不可能"猜对"------必须发请求看响应才知道。
所以问题不是 AI 写得不够好,是 AI 写完之后没人帮它跑一遍。这事过去都是 reviewer 在干。reviewer 装真机,发现 bug,反复沟通修复,再 review。AI 提交一个 PR,reviewer 帮它"现场 debug 三轮"------这种循环在团队里能消耗大量精力。
二、思路:那能不能让 AI 自己先验收一遍?
如何让AI能更好地闭环整个开发流程,更长时间的自主工作,而不是等着人类程序员帮他验证? 这就是我们重新想这个问题的起点。
不去优化 AI 写代码的能力,去给它一套"自己跑代码"的工具。AI 写完之后,能像人类工程师一样:先静态检查一遍,再单独看一下 UI 渲染对不对,最后装到真机上点一下、看一眼日志。如果哪一步出了问题,自己抓出来修掉,再来。
但"跑代码"不是单一动作------不同的 bug 在不同时刻、用不同方法抓最划算:
| Bug 出现在哪 | 最快抓住的方式 | 反馈速度 |
|---|---|---|
| 编译时 / 类型 / 架构规则 | 静态检查 | 秒级 |
| 单 Composable 视觉(字体 / 间距 / 色彩) | 离屏渲染对照 Figma | 秒级 |
| 集成 / 真接口 / 真硬件 | 真机跑 | 一两分钟 |
任何一种工具------哪怕是 AI------也没法用同一种范式覆盖所有。没有银弹,但可以分层。
每一层只抓最适合自己的部分,三层叠加就能在 review 之前覆盖到------这就是闭环的核心思想。
三、方案:三层独立的"门"
暂时无法在飞书文档外展示此内容
我们在仓库里建了三道独立的门,每一道门只检查最适合自己的问题。
L0:静态门------抓"基本运行性"问题
一条命令把"基本运行性"问题全部抓掉:
go
make verify
背后是一条链:编译 → 全模块单测 → lint → 架构规则 → 代码规约。秒级反馈,固定到一个 build flavor 避免跑 4 套组合。
但工具本身不是关键。关键是配套的一条规约 :失败 3 轮内自己修不掉就停下来输出诊断报告,等人介入。
这条规则比工具本身重要------它逼出"先查官方 doc 再改"的行为。如果不卡 3 轮上限,AI 容易陷入"试错 → 越改越糟 → 再试错"的循环。3 轮的"心理压力"让它第一时间会去翻文档,而不是盲试。
L1:视觉门------抓 UI 跟设计稿的偏差
每个 Composable 单独渲染成 PNG,AI 拉 Figma 节点对比,按 P0 / P1 / P2 输出差异。秒级反馈,无需设备。
技术上用 Roborazzi(基于 Robolectric),不用 Paparazzi------后者跟新版 SDK / JDK 有兼容性问题。
关键决策:不入库 PNG baseline 。一旦 baseline 记录的是错误实现,bug 被凝固进 git,后续维持原样的改动都过测------这是"基线模型"的常见陷阱。Figma 永远是当下真值,不需要"基线"。
L2:真机门------抓集成 / 真接口 / 真硬件
一条命令直达任意页面:
go
make goto ROUTE=vonder://news/detail?id=xxx
debug 构建里塞了一个特殊 Activity 接收 deep link,通过项目自有的 navigator 派发。配 mobile-mcp(一个开源的 MCP 服务,让 AI 能截屏 + 读 accessibility 元素 + 操作模拟),AI 自己跳页、截屏、看 logcat 真接口请求/响应。
L2 慢(一两分钟),所以只兜底快循环兜不住的:BLE / 真字体 / 跨屏导航 / 真录音流 / 设备物理状态。
缺一不可的实证
可能你会想:是不是 L0 + L1 就够了,L2 太重?
两个真实事件------都是 L0 + 单测全过的 commit:
事件一 :装到真机一启动 → errno=40001(缺 user_id header)
事件二 :装到真机一点击 → IllegalStateException(漏配某框架隐式约定)
如果只有 L0,这俩问题都得到 reviewer 阶段才发现。如果只有 L1,也抓不出来------L1 抓的是单 Composable 视觉,不抓真接口、不抓启动 crash。
L0 + L2 是底线,三层全有更稳。
四、几个绕不过的设计选择
设计这套闭环时反复纠结过几个 trade-off:
真机优先 over emulator。BLE / 录音 / OTA 在 emulator 上没意义。我们直接假设开发机持续连接真硬件------边缘场景(解绑 / 低电量 / 走出范围)由人兜底,但 happy path AI 自己跑。
快循环要做厚,慢循环只兜底。L0 + L1 抓尽量多,L2 只跑前两层抓不住的。这是反馈速度 vs 覆盖广度的取舍。
Plan Mode 卡死触发条件:改 ≥ 100 行 / 跨 3 模块 / 改构建脚本 / 改 schema / 引新依赖 / 调核心业务时,AI 必须先输出实施计划等人确认。
后面会讲到一个真实的"被推翻"瞬间------那是 Plan Mode 真正的价值。
五、实战:两个 feature 的故事
为了验证闭环能不能真的跑通,挑了两个真实需求让 AI 跑全流程。
故事一:列表页踩到三处 spec 偏离
任务:新闻列表 + cursor 分页 + 加载更多 + 下拉刷新。代码层面没什么挑战。
L0 全过,L2 装到真机一 fetch:
ini
errno=40001 user_id header is required
AI 看 OkHttp 请求头日志才确认:spec 写的是 user_id(下划线),HTTP 习惯是横线,spec 自己的 debug bypass 写的也是横线版本------AI 默认按 HTTP 习惯选了横线,服务端只认下划线。
修完再跑,又遇到 NPE。原来项目里用的 JSON 库不认 Kotlin defaults,DTO 缺字段直接赋 null。改成 nullable + mapper 兜底。
第三轮发现 score 字段类型不对------spec 说 0~1 float,服务端返回 99。Float? 容错收下。
整轮三处真服务端契约偏离 spec,全部 L2 真机抓出,单纯靠 L0 永远抓不到。

故事二:被 L1 一眼抓出的字体偏差
详情页 L0 一次过,L2 真机一开就崩------某 BaseActivity 子类要求根 View 必须有特定 id,无文档约定。10 分钟内修。
后来加了 L1 离屏渲染,第一次跑 NewsCard 和 NewsDetailScreen 各 4 个 variant 渲染成 PNG,跟 Figma 对比立刻发现:Figma 标题用衬线字体(GT Canon),实现用了 Compose 默认无衬线。
这个偏差 L2 真机截图也能看出来,但嵌在完整 UI 里,肉眼对比 Figma 容易把字体差异当"渲染抖动"漏过。L1 单 Composable 离屏图焦点集中------第一眼就发现。
L1 的真实价值在这里:抓 L0 抓不住、L2 看不仔细的视觉偏差。
六、效果:reviewer 的精力终于不用花在"它能不能启动"上
两个 feature 的量化数据:
| 列表页 | 详情页 | |
|---|---|---|
| 业务代码 | ~1000 行 | ~2200 行 |
| 单元测试 | 19 个 | 14 个 |
| L0 重试轮数 | 2 轮 | 0 轮(一次过) |
| L2 真机抓出 | 3 处 spec 偏离 | 1 crash + 1 视觉 bug |
| L1 抓出 | --- | P0 字体偏差 |
从体感上,开发者最后拿到已经的是一个质量比较高,bug比较少的产物。
但更关键的是 review 关注点的迁移:
没 Agent-Loop 时
reviewer 经常在帮看:能不能编译过 / 能不能启动 / 真接口能不能通/UI是不是符合预期,要反复沟通问题和验收修复......基础运行性占了大量精力。
有 Agent-Loop 时
这些问题在提交前已经过双门验证。reviewer 重点放在:业务逻辑边界、设计模式、长期可维护性------真正应该花精力的地方。
七、经验怎么传给下一个 session
工具有了、规约有了,但下一个新 session、新 AI、新协作者来怎么办?
光写 markdown 不够------文档不会自动出现在合适的时机。
我们做了双形态沉淀:
第一种是 Skill(自动触发)。把"开发新 feature"的标准流程写成一个有强 trigger 描述的 skill,覆盖关键词如"实现 X / 加 Y / 接入 Z 接口"。新 session 一开始说"做 X 功能",AI 自动看到这个 skill 的内容------7 步流程 + 14 条踩过的坑 checklist。
第二种是 Markdown 镜像(跨工具兼容)。同样的内容以普通 markdown 形式存在 docs/ 下。Codex / Cursor / 直接读文档的人------任何不支持 skill 的工具都能等价访问。
第三种是 复盘模板(结构化沉淀)。每个新 PoC 暴露的新坑,按统一模板写一段进 doc。下次同领域 feature 受益。
核心理念:经验必须以"可触发"形式沉淀。光写 markdown 不够,要有 skill 让 AI 在合适时机自动看到。
八、最后
回头看这个过程,最深的几条体会:
- 不要试图让 AI 写"完美的代码" 。让它先把代码跑一遍,再交人。
- 服务端契约偏离 spec 是常态,不是例外。OpenAPI 是设计文档,不是行为契约。
- 3 轮重试上限的"心理成本"很重要。AI 第一时间会去搜官方 doc 而不是盲试。
- Plan Mode 的价值是把决策摆出来,不是机械确认------给人 redirect 的空间。
- L0 + L2 是底线,L1 不可省略------三种 bug 三种最佳抓住时机。
Agent 自主开发不是终点。它是让 AI 能"接近人类工程师水平"做事的最低门槛。
更上层的目标------AI 主导的架构设计 / 跨 feature 的代码 review / 长期 tech-debt 治理------都需要先有这层"自己跑自己代码"的基础闭环。
让 AI 学会自己 review 自己,是更复杂 AI 工程实践的起点。