都是 AI Coding,为什么 Java 体验差了一个量级?五条方法论帮你构建自己的 Harness 环境
都是 AI Coding,为什么 Java 体验差了一个量级?五条方法论帮你构建自己的 Harness 环境
来源类型: 微信公众号文章
日期: 2026-06-02
标签: AI, 记忆系统, 办公效率, 编程, 区块链, 大模型
原文链接: https://mp.weixin.qq.com/s/3-hQ4vHYErfpIzYPC6wJwg
摘要
- 体验差距到底在哪 在依赖比较轻的项目里(比如前端、CLI 工具、纯本地的 Python 脚本),AI Coding 的体验是这样的: - 编辑代码 → 本地运行 → 测试验证 → AI 读取结果 → 自动修复 → 再次验证 → ... 整个循环在本地完成,不需要人盯着。你甚至可以用上 autoresearch 的思想,和 AI 说:「帮我持续优化迭代这个功能,排查并修复bug,至少10轮」,AI Agent 就可以自己迭代几十轮,直到功能跑通。 但 Java 微服务项目里,这个闭环基本是断的。 这周让 AI 给一个 Agent 应用加一个新的 Tool 实现,逻辑不复杂,大概 15...
核心内容
- 体验差距到底在哪
在依赖比较轻的项目里(比如前端、CLI 工具、纯本地的 Python 脚本),AI Coding 的体验是这样的: - 编辑代码 → 本地运行 → 测试验证 → AI 读取结果 → 自动修复 → 再次验证 → ...
整个循环在本地完成,不需要人盯着。你甚至可以用上 autoresearch 的思想,和 AI 说:「帮我持续优化迭代这个功能,排查并修复bug,至少10轮」,AI Agent 就可以自己迭代几十轮,直到功能跑通。
但 Java 微服务项目里,这个闭环基本是断的。
这周让 AI 给一个 Agent 应用加一个新的 Tool 实现,逻辑不复杂,大概 150 行代码。AI 写完之后很自信地说"已完成",但它没有任何办法验证自己写的东西能不能跑。因为这个项目依赖 OSS、远程沙箱、HSF 一整套云端基础设施,本地 mvn spring-boot:run 直接启动失败。
于是进入了经典循环:我把代码推到预发,等 5 分钟部署完成,手动触发一次调用,发现 NPE,截图贴回给 AI,AI 改了两行,我再推预发,再等 5 分钟……三轮下来半小时过去了,改的只是一个参数注入顺序的问题。
如果是一个本地能跑的项目,AI 自己跑一下测试就能发现这个错误,整个修复可能 30 秒就结束了。同样的模型、同样的 Prompt,差距不在 AI 能力,在如何构建 AI 友好的工程环境,即 Harness Engineering。
- 问题的本质
这个体验差距不是偶然的,根源在微服务时代的技术架构。
通常 Java 项目都重度依赖云端微服务基础设施:HSF 做服务调用、TDDL 做数据库路由、Diamond/Switch 做配置下发、MetaQ 做消息队列。一个看起来很简单的 @Autowired,背后可能牵着一整套分布式基础设施。这些东西在云端跑得很好,但在本地全部不可用。
说白了,微服务架构天然不 AI 友好。AI Coding Agent 需要一个能在本地跑起来的环境来验证自己的输出,但微服务架构把运行时依赖全部推到了云端。项目在本地跑不起来,AI 就没办法自主验证,只能靠人去推预发、看结果、再反馈。
于是就出现了一个很常见的工作流:
-
-
-
本地 Vibe Coding 开发 → 推预发部署 → 人工在预发环境验证结果 → 人工把验证结果反馈给 AI → AI 继续改 → 再次推预发 → 再次人工验证 → ...
人在每个环节都是阻塞点。跟那些本地能直接跑的项目里 AI 能自己迭代几十轮比起来,Java 微服务项目里 AI 每做一步都得等人。
最近社区关于 Harness Engineering 的讨论很多,大家聊的焦点通常在 CLAUDE.md 怎么写、lint 规则怎么配、验证脚本怎么跑。但对于 Java 微服务项目,还有一个更基础的问题:项目在本地能跑起来吗? CLAUDE.md 写得再好,AI 连代码能不能编译通过都验证不了,后面的一切都是空谈。
这篇文章聊的就是这个问题:怎么让 Java 微服务项目的本地环境变得 AI 友好,让 AI 拥有自主验证的能力。做法来自一个 Agent 应用的实际改造,算是抛砖引玉。
- 三条改造原则
我在这次改造中总结出三条原则,后面逐个展开。
3.1 依赖倒置,接口先行
上层逻辑依赖抽象接口,不依赖具体实现。云端和本地只是接口的不同实现。
这是经典的依赖倒置(DIP),但在本地 Harness 的场景下有了实际的意义:它决定了你的系统能不能在本地跑起来。
拿我的实际改造举例。线上环境中,Agent 的文件系统(AgentFs)基于 OSS 对象存储,命令执行走远程沙箱(Sandbox),两者通过 OSS Mount 共享文件系统。这套架构在云端跑得好好的,但本地完全不可用,没有 OSS 服务,也没有远程沙箱。
改造前的依赖关系:
-
FilesystemService → OssStorageAdapter (具体类,直接调 OSS SDK)AgentWorkspace → SandboxCommandExecutor (具体类,调远程沙箱 API)
上层直接依赖了具体的云端实现,想在本地跑就得把整个 OSS 和 Sandbox 搬过来。不现实。
改造后,我抽了一个 StorageAdapter 接口,把上传、下载、判断存在、删除、列举这些操作统一定义好。线上的 OSS 实现加一行 implements StorageAdapter,本地新写一个 LocalStorageAdapter,用 java.nio.file 把逻辑 key 映射到本地路径。工厂类在创建文件系统时,检测到有本地参数就走本地实现,否则走 OSS 实现。
-
-
-
StorageAdapter (接口) ├── OssStorageAdapter (线上,走 OSS SDK) └── LocalStorageAdapter (本地,走 java.nio.file)CommandExecutor (接口) ├── SandboxCommandExecutor (线上,调远程沙箱 API) └── LocalCommandExecutor (本地,ProcessBuilder + bash -c)
上层所有用到文件操作和命令执行的代码完全不用改。它们只依赖接口,不关心底层是 OSS 还是本地文件系统。切换运行环境就是换一个接口实现,不用重写上层逻辑。
3.2 零侵入,Profile 隔离
本地改造不能让线上代码路径多走一行额外的代码。
这条容易被忽视。很多团队做本地化适配时,会在主流程里加 if (isLocal) 分支,线上代码路径变复杂了,出问题的风险也跟着上去。
我的做法是严格的零侵入:
Spring Profile 隔离:本地专属的 Bean 通过 @Profile("local") 装配,线上专属的通过 @Profile("!local") 守卫。两套配置编译期就彼此不可见。
-
-
-
-
-
- @Configuration@Profile("local")public class LocalRepositoryConfig { @Bean CommandExecutor localCommandExecutor() { ... } @Bean("localFsBasePath") String localFsBasePath() { ... } // 用 AtomicLong 替代 TDDL GroupSequence @Bean("sessionSequence") Sequence sessionSequence() { return new LocalSequence(); }}
同时在本地启动入口通过 @ComponentScan 的正则过滤,把线上专属的包(远程沙箱、
...(内容截断,完整内容请查看原文)
本页由自动化脚本生成,待人工审核补充