自从用 Claude 完成了 Graphite – 基于 JVM 字节码的静态分析框架,工程质量和解决问题的方法和效率着实把我给震撼到了,然后又让它用 Rust 写了 Rustyman – 面向 AI 的网络代理服务,效果也很是出奇的好,名副其实的 10x 工程师。于是,便产生了一个大胆的想法 – TestPilot,在纯 JVM 上运行 Android APK 。拿到需求后,它便进入了 Plan 模式,根据对需求的理解提出了双层架构的方案,然后规划了 Road Map,分了 3 个阶段来进行,从 Phase 1 - MVP 到 Phase 3 - Production Ready。在 MVP 阶段表现跟前两个项目一样丝滑,到了 Phase 2 一开始风风火火的重写各种 Android 系统类,也没觉得有什么不对,逐步完了 Activity, View, Fragment, ViewPager, RecyclerView 的重写,接下来它提出要重写 ConstraintLayout 的时候,我意识到不对劲了– 重写系统类我能理解,但为什么要重写一个二方库?ConstraintLayout 并不属于 Android Framework?于是便跟它一起仔细地 review 了整个方案,才发现它对底层依赖的 layoutlib 的能力不太了解,才提出重写系统类的方案,这不禁让我开始思考一个问题:Claude 的能力边界到底在哪里?

从一次对话说起

我问 Claude:

为什么你在 Graphite 上的表现比 TestPilot 好那么多?

它的回答让我有些意外。它没有给出那种”AI 有局限性”的套话,而是非常具体地分析了两个项目在问题性质上的本质差异:

Graphite 我能做好的原因:问题是组合式的。静态分析 = 解析 + 遍历 + 匹配。每一步都有成熟的库和明确的 API 边界。

TestPilot 我挣扎的原因:问题是仿真式的。要模拟 Android 运行时行为,但 Android 运行时是个巨大的黑盒,有大量隐式契约。

它用了一个比喻:Graphite 是在一个有规则的棋盘上下棋;TestPilot 是在模拟一个它没完整见过的物理系统。

这个自我认知的准确程度,说实话,超出了我的预期。

问题域的确定性

顺着它的思路,我整理了一下两个项目的差异:

Graphite TestPilot
问题定义 清晰、封闭 模糊、开放
边界 明确 (JVM bytecode → 调用图) 模糊 (Android 整个运行时)
参考实现 有 (SootUp, ASM) 无完整先例
成功标准 可验证 (找到的常量对不对) 难验证 (行为”像”Android 吗)

Graphite 的每一步都可以增量验证:写一个查询,跑一下,结果对不对立刻知道。而 TestPilot 的难点在于——View.measure() 该怎么工作?文档说得很笼统,真正的行为藏在 AOSP 源码里,而且版本间有差异。

这让我想到了《为什么伟大不能被计划》中提到的一个观点:目标越明确,路径越清晰;目标越模糊,越需要探索。 AI 在前者上表现出色,在后者上则需要人类的引导。

Claude 的局限性

在对话中,我直接问 Claude:你在软件工程方面有哪些局限性?

它的回答相当坦诚:

  • 上下文的断层感——它看不到完整代码库。即使把代码贴给它,它也缺乏 IDE 那种”活”的理解,比如一个类被哪些地方引用、某个改动会不会破坏下游模块。
  • 没有真正的执行反馈——它能生成代码,但它不会”跑”代码然后学习。这意味着它在对话之外是断掉的反馈循环。
  • 生产环境的盲区——真实的 bug 往往藏在时序、并发、设备差异里。它可以帮分析堆栈,但复现条件、设备碎片化这些”脏活”它只能推测。

这些局限性其实不难理解。但接下来它说的一句话让我印象深刻:

你把我当一个随叫随到但每次都需要重新 onboard 的 Staff Engineer 用,期望就比较合理。

这个定位非常准确。

弥补局限的方法

既然知道了局限在哪里,下一步自然是想办法弥补。我问 Claude:有什么办法能弥补你的这些局限?

它给出了几个思路:

  • 把结构化知识喂给它——比如 Graphite 产出的调用图谱,如果能导出成可读格式,贴给它时建议会精准很多。不需要完整代码,只需要”骨架”。
  • 把结果回流给它——跑完它建议的脚本后,告诉它”这个在 module-x 上跑了 3 分钟,干掉了 47 个 dead branch,但漏了 lazy-init 的场景”——这就是手动的反馈循环。
  • 用 Claude Code 做闭环——Claude Code 能执行、看报错、自己迭代,正好补了”不能跑代码”的短板。对话式的 Claude 适合前期方案设计和 review,Claude Code 适合执行层。

其中最有意思的是它提到的一个本质性思路:

不需要我记住一切,只需要我能查到一切。

如果 Graphite 能回答”这个类被谁依赖”,它就不需要记住依赖图——它只需要知道怎么问 Graphite。理想状态是:我问问题 → 它调用我的工具拿数据 → 基于数据回答。

这让我想到了 MCP(Model Context Protocol)。把 Graphite 的查询接口暴露成 MCP server,Claude 就能”实时查”而不是”靠记忆”。

大型代码库的 Onboard

我还问了一个实际问题:对于一个存在了 10 多年的代码库,你最高效的 onboard 方式是什么?

它的回答出乎我的意料:不是从代码开始,是从”力的分布”开始。

它建议我先回答这些问题:

  • 什么东西改起来最慢/最怕?
  • 最近 3 个月的 incident 集中在哪些模块?
  • 新人上手最常踩的坑是什么?

这比架构图有用。10 年的仓库,架构图大概率是过时的或理想化的。但痛点是真实的,能帮它快速定位哪些区域值得深入。

它还建议我写一份”生存指南”而非文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
# Project Survival Guide

## 模块心智模型
- core-network: 所有 API 调用的唯一出口,别绕过它
- legacy-payment: 正在迁移到 payment-v2,新代码不要依赖

## 历史包袱
- analytics 模块有 3 套 API,只用 AnalyticsV3
- 有些 AB 实验 flag 永远返回 true,但代码没删

## 地雷
- 不要动 BaseActivity,继承链太深
- ProGuard rules 碰之前先跑 release build

这种文档 500 行以内,但能让它在 5 分钟内变得”可用”。

思考

这次对话让我对 AI 辅助开发有了更清晰的认知。

  • AI 的能力边界由问题域的确定性决定。问题越封闭、边界越清晰、验证越容易,AI 的表现就越好。反过来,如果问题是开放的、需要模拟未知系统的行为,AI 就会挣扎。
  • 弥补 AI 局限的关键不是让它记住更多,而是让它能查到更多。外部化 context,按需注入,让 AI 成为工具链的一部分而非独立的 oracle。
  • 最高效的人机协作模式是互补而非替代。AI 擅长快速原型、代码审查、解释复杂概念、生成样板代码;人类擅长判断模糊需求、理解组织政治、维护长期的项目心智模型。

Steve Jobs 说过:

The computer is a bicycle for the mind.

AI 是这辆自行车的升级版。但骑车的人,依然是我们自己。