当 Agent 开始写代码,程序员还剩下什么?

过去写一个功能,大致是这样的流程:

理解需求 → 设计数据结构 → 写代码 → 调试 → 联调 → 修 Bug

现在越来越像另一种场景:

描述需求 → Agent 搜索 → Agent 修改十几个文件 → Agent 跑测试 → 功能出现了

更微妙的是,过去很多“懒得补齐”的工程细节 —— DTO、Adapter、migration、SQL、OpenAPI、类型生成、权限校验、测试脚手架 —— 现在 Agent 也可以顺手补上。于是问题不再只是“写代码更快了没有”,而是另一件更根本的事:当 Agent 已经可以承担大量实现工作后,程序员最应该把精力放在哪里?

最开始我给自己的答案也很模糊:架构、设计、审查。

后来我越来越觉得,这些词都太虚了。真正剩下的,其实只有一个更具体的东西:判断。

准确地说,是判断什么值得实现,什么值得成为系统的事实,什么值得被固化为长期约束。后来我才意识到,这些判断最终都指向同一件事:对业务进行正确抽象。

Agent 最危险的地方,不一定是写错代码

我早年接手过一套填报审核系统。现在回头看,这个系统最重要的并不是 Kotlin、Spring Boot、MyBatis 或 MySQL,而是我当时对业务作出的那套抽象。

系统表面看起来很简单:管理员配置活动、公司和表单 → 填报员代表公司填写并提交 → 审核员评分、通过或打回 → 填报员修改后重新提交 → 查分、归档和导出

真正困难的地方,不在 CRUD,而在撤回、审核撤回、打回、重新提交、活动阶段回溯,以及这些行为与角色、公司归属、表单和填报状态之间的关系。

当时客户提出填报需要“打回”,我做了一个看起来很合理的解释:只解锁被打回的行,未被打回的行继续锁定, 以及一个很自然的技术实现:给表的每一行都加上状态。

这个决定此后迅速扩散到整个系统,使得填报状态机迅速膨胀,数十个围绕着填报数据的接口都要考虑各种状态下能够进行的操作,随着开发迭代,这一决策积重难返,成为沉重的技术债务。

这里最值得反思的,不是“我写了一个 Bug”。

恰恰相反,这个系统很多年都可以稳定运行。真正的问题在于:我把“打回的业务意图”过早解释成了“逐行加锁”这个系统事实。

于是我越来越意识到:Agent 最危险的地方,不一定是写错代码;而是它可以极快地把一个错误判断实现得非常完整。

代码越便宜,这个风险反而越大。因为一个不够成熟的判断,不再只是留在脑子里,而是会在几分钟内扩散成数据库、Service、API、前端和测试。

程序员剩下的,首先是决定什么是真的

如果实现越来越容易,那么程序员真正要负责的,到底是什么?

我现在会把它拆成几个问题:

  • 系统真正管理的对象是什么?
  • 数据属于谁?
  • 什么状态是业务事实,什么只是当前 UI 的表现?
  • 什么规则无论需求怎么变化都不能违反?
  • 哪些字段半年以后很可能完全不同?
  • 谁有权解释“最终结果”?

这些问题的共同点是:它们都不是“怎么写代码”的问题,而是“什么应该成为系统事实”的问题。

所以我越来越倾向于用一句更直接的话来理解架构:架构,本质上是在分配刚性。

哪里应该硬,哪里应该软;哪里应该早一点固化,哪里应该继续保持弹性;哪里应该建立唯一事实来源,哪里应该承认自己还没想清楚。

过去我们有时把这种东西叫“品味”。现在我更愿意把它叫作:对系统抽象和变化方向的判断力。

判断一:不要把偶然样本变成永久事实

在我负责的一个产品运营系统中,Excel 导入就是一个很典型的例子。

如果让 Agent 根据业界最佳实践,它会自然而然地这样设计:

Excel 有哪些列 → 数据库建哪些列 → SQL → API → 页面字段

这个过程一点也不荒谬。它甚至很“工程正确”。

问题在于,真实世界里的 Excel 从来没这么听话,而我面临的情况更是如此。客户会改表头,会增加列,不同来源的文件还会携带不同的额外信息。于是你真正要判断的,就不是“该不该建表”,而是:

  • 哪些字段代表系统的稳定事实?
  • 哪些只是当前某一份输入样本碰巧带进来的信息?

最后我们采取的是另一种方式:

Excel 表头 → 映射少数稳定语义 → 身份、关联、过滤和约束字段进入正式列 → 其余原始字段完整保存到 PostgreSQL JSONB → 同时保留原始文件、Sheet 和来源行号

这里并不是“少做建模”,而是拒绝在证据不足时过早宣布某些字段已经稳定。

稳定事实需要刚性:

  • PostgreSQL 约束
  • migration
  • SQL 查询口径
  • sqlc 编译
  • 后端事务与计算
  • 结果版本和历史快照

频繁变化的外部字段则需要弹性:

  • 表头别名映射
  • JSONB 原始值
  • 导入血缘
  • 等查询需求真正稳定后,再提升为正式列

这件事让我越来越确定一句话:正确抽象,并不是消除所有变化,而是把变化留在代价最低的边界内。

判断二:不要为了局部优化制造第二个真相

另一个例子来自产品运营系统的核价功能。

Agent 一度试图在前端实时计算数百条 BOM 的单价。它给出的理由其实也很合理:避免频繁把大量数据发送给后端,减少传输和等待。

如果只盯着“性能”这个局部问题,这个建议并没有明显错误。

但我最后否掉它,不是因为“算不过来”,也不是因为“前端不能算”,而是因为另一个更重要的问题突然变得非常清楚:谁有权定义最终核价结果?

如果答案是后端,那么选价、替代料、汇率、加工费、管理费、覆盖率、阻塞原因和规则版本就必须保持唯一。让前端复制一整套完整计算规则,等于在系统里放入第二个事实解释器:前端规则 ≈ 后端规则

这个等号听起来轻描淡写,实际却是一个需要永久维护的承诺。你必须持续证明它们相等,而架构本身却无法替你保证这件事。

于是这个问题就不再是“能不能优化一次请求”,而是:为了节省一点局部运行成本,值不值得引入长期的一致性成本?

所以我后来越来越相信一句话:不是每个可以优化的问题,都值得被解决。

Agent 特别擅长解决被明确描述的局部问题。程序员真正要保护的,则是那些更重要的不变量 —— 唯一事实来源、单一解释权、错误成本、长期一致性。

但人也不能一开始就知道“正确答案”

如果文章到这里为止,很容易滑向一种过于自信的叙事:好像人天然就知道正确抽象,Agent 只是照着执行,但这远不是 AI 人机协作的答案。

现实复杂的多。

在产品运营系统的核价报价功能里,我自己一开始也不知道正确抽象是什么。

最初真正不确定的,并不是数据库有几张表,而是:

  • 用户从哪里进入;
  • 谁负责核价,谁负责报价;
  • 核价和报价是如何协同工作的;
  • 核价所需要的 BOM、单价、汇率、PN 来自哪里;
  • 哪些数据允许覆盖,哪些数据是唯一事实;
  • 是否应该用一个页面承担所有核价逻辑;
  • 报价和核价应该用数据快照还是数据引用;

这些东西如果一开始就靠猜来设计数据库和 API,很容易把尚未理解清楚的流程偶然固化成永久结构。

所以我后来换了一个顺序:先用 Mock 接口把完整流程跑起来,再从流程里提取真正稳定的东西。

可以把这个过程概括成下面这张图,我想保留它,因为它比一堆术语更接近我真实的工作方式:

它想表达的不是“前端先行”,也不是“UI 比数据库更重要”,而是另一件事:复杂业务应该优先在不确定性最大的地方被验证。

更完整一点,就是:Vibe 原型 → 完整走通业务流程 → 暴露状态、异常和数据需求 → 提取业务不变量与变化边界 → 固化数据库、SQL、Service 和 API → 接入真实数据继续校正

这里的 Mock 并不是“临时假后端”,而是一份可执行的业务假设。它的作用不是直接成为最终事实,而是帮助人看清楚什么值得成为事实。

所以,人的价值并不是“一开始就知道正确答案”,而是:知道什么时候还不应该把答案固化。

发现之后,才轮到机器把判断落实

当复杂业务经过流程验证,某些东西开始稳定下来,机器的价值才真正开始放大。

这时候,SQL generation、API generation、类型生成、测试,不再只是“提高效率的工具”,而是在承担一个更重要的职责:把人的判断,变成机器可以拒绝违反的约束。

也因此,我越来越把整个过程理解成三个阶段:探索 → 判断 → 固化

分别对应:Vibe UI → Human judgment → SQL Schema, Query / OpenAPI → Agent implementation

这种理解也改变了我评价技术方案的标准。过去我会更在意一种框架能替开发者少写多少代码;现在我开始更在意,当判断已经形成之后,它能不能被清楚地表达、追踪和验证。

过去,为了让开发者 easy,我们愿意接受很多框架替我们隐藏的复杂度。Spring 做得非常好:写注解、加配置、接依赖,很多能力就自动连起来了。

但 Agent 时代,样板代码已经很便宜。相反,真正昂贵的是那些藏在运行时里的因果链。一个行为为什么发生,最好能够从入口、Service、SQL 一直追踪到数据库,而不是依赖某个容器、某个代理、某组配置在暗中维持语义。

于是我越来越倾向一种简单的目标:

  • Developer Easy:让人主要关注高价值判断;
  • Agent Simple:让 Agent 面对显式、可搜索、可局部理解和复现的因果链;
  • Application Clean:让应用本身保持可靠、可诊断、可应对变化的结构。

这里的 Clean 不是特指某一种分层教义,而是更朴素的意思:一个行为为什么发生,可以沿着最短路径被追踪清楚。

回到标题:当 Agent 开始写代码,程序员还剩下什么?

如果只用一句话来回答,我现在会这样说:程序员剩下的,不是“把代码写出来”,而是决定什么值得被写成系统事实。

更具体一点,程序员真正负责的是这些判断:

  • 系统真正解决什么问题;
  • 什么必须稳定;
  • 什么应该保持弹性;
  • 谁拥有事实;
  • 哪些业务耦合必须显式保留;
  • 哪些错误必须尽早失败;
  • 哪些复杂度根本不值得引入;
  • 什么时候继续探索,什么时候开始固化。

而 Agent 负责的是另一件事,把这些判断:

  • 变成 migration
  • 变成 SQL
  • 变成 generated queries
  • 变成 Service
  • 变成 API adapter
  • 变成前端类型
  • 变成 UI
  • 变成 tests

这也是为什么我越来越觉得,Agent 时代真正稀缺的不是写代码的速度,而是另一种能力:判断在哪里探索,在哪里保持弹性,在哪里建立唯一事实,以及什么时候应该开始固化。

对简单 CRUD,可以从稳定数据向外展开。

对复杂业务,先让用户完整走一遍流程,再从行为中提取状态、数据和边界。

用 SQL 固化内部事实,用 OpenAPI 固化外部契约,用显式装配保持可靠因果链,用 Agent 承担大规模的确定性实现。

Agent 让代码越来越便宜,但它没有让判断变便宜。恰恰相反,当一个判断可以在几分钟内被扩散成数据库、Service、API、前端和测试时,判断本身比过去更昂贵了。

业务抽象由人判断,事实与契约由机器强制,连接与实现由 Agent 承担。