这篇文章不讨论“AI 会不会取代程序员”这种大问题。

只做一件事儿:把一个脱敏后的真实开发工单交给 AI Agent,让它从读需求、找代码、改逻辑、补测试、修报错,一路跑到生成 PR 摘要。

过去两年,很多程序员小伙伴对 AI 编程的印象还停留在 Copilot 补全函数、ChatGPT 写工具类、让模型解释一段祖传代码等。

但 2026 年之后,真正值得关注的变化已经不是“AI 能不能写几行代码”,而是:AI Agent 能不能接住一个完整的小工单?

换言之,它到底只是一个高级代码补全器,还是已经像一个能被管理的初级开发者?

我做了一次实验。

发现:AI Agent 还不能放心地“独立负责需求”,但它已经可以承担一部分真实开发流程。它最像的不是资深工程师,而是一个效率很高、很听话、但需要严格验收的初级外包程序员。

你不能只给它一句“帮我修一下”。你要像技术TL一样给它目标、边界、验收标准和 Review 意见。

这篇文章会完整复盘这次实验。


一、实验工单:不复杂,但足够真实

我选的不是“写一个 Todo Demo”,也不是“从 0 生成一个项目”,而是一个更接近日常工作的低风险小工单。

需求如下:

修复订单列表接口的一个状态过滤问题:当 status=all 时,不应该过滤掉已取消订单。

给订单列表返回体新增字段 isEditable,用于前端判断订单是否可编辑。

补充后端单测,覆盖 allcanceleddraft 三类状态。

修改前端表单手机号校验逻辑:手机号格式不合法时,禁止提交。

技术栈是一个常见的前后端组合:后端使用 NestJS、TypeORM、Jest;前端使用 Vue 3、Element Plus、Vitest。

这个工单看起来很小,但它覆盖了真实开发里很典型的一条链路:理解业务语义、定位接口逻辑、修改 DTO、补测试、处理前端校验、跑测试、根据报错继续修。

也就是说,它不是考 AI 会不会写代码,而是考它能不能跑流程。


二、我给 Agent 的第一条指令

我没有直接说“帮我改代码”,而是把它当成一个新接手需求的开发同学来交代任务。

你是本仓库的开发者,请完成一个真实工单。

目标:
- 修复 GET /orders 在 status=all 时错误过滤的问题
- 响应新增 isEditable 字段
- 补充后端单测
- 前端手机号格式非法时禁止提交

约束:
- 不改数据库 schema
- 不破坏已有字段含义
- 仅修改与订单列表、表单校验相关的文件
- 如果发现需要扩大修改范围,先说明原因,不要直接改

验收标准:
1. status=all 返回全部状态订单,包含 canceled
2. status=canceled 仅返回 canceled
3. status=draft 仅返回 draft
4. 返回体包含 isEditable: boolean
5. 手机号格式不合法时前端表单不能提交
6. 所有相关测试通过

请最终输出:
- 改动文件清单
- 每个文件的改动理由
- 风险点
- 回滚方案
- 需要人工确认的业务点

这里有一个很关键的点:我没有把实现方案喂给它,而是给了它目标、约束和验收标准。

如果只写“修一下订单接口 bug”,Agent 很容易做出看起来合理、实际却越界的修改。比如顺手改 DTO、改枚举、改数据库字段,甚至把业务规则抽象成一套它自己想象出来的通用方案。

AI Agent 的问题不是“不够努力”,而是“太努力”。你不给边界,它会自己补边界。


三、第一轮改动:它确实能自己找到关键位置

Agent 先定位到了订单列表的 Service 层,大致逻辑如下。

原来的代码问题是:只要传入了 status,就会追加状态过滤条件。

async listOrders(query: ListOrdersQueryDto) {
  const qb = this.orderRepo.createQueryBuilder('o')
    .where('o.userId = :userId', { userId: query.userId });

  if (query.status) {
    qb.andWhere('o.status = :status', { status: query.status });
  }

  const rows = await qb.orderBy('o.createdAt', 'DESC').getMany();
  return rows.map((row) => this.toOrderVO(row));
}

问题就在这里:status=all 本来代表“全部状态”,但旧逻辑会把它当成一个具体状态去查。

Agent 的第一轮修改是:只有当 status 存在且不等于 all 时,才追加过滤条件。

async listOrders(query: ListOrdersQueryDto) {
  const qb = this.orderRepo.createQueryBuilder('o')
    .where('o.userId = :userId', { userId: query.userId });

  // status=all 表示全部状态,不追加状态过滤条件
  if (query.status && query.status !== 'all') {
    qb.andWhere('o.status = :status', { status: query.status });
  }

  const rows = await qb.orderBy('o.createdAt', 'DESC').getMany();
  return rows.map((row) => this.toOrderVO(row));
}

同时,它在 VO 转换逻辑里新增了 isEditable

private toOrderVO(row: OrderEntity): OrderVO {
  const isEditable =
    (row.status === 'draft' || row.status === 'rejected') && !row.lockedAt;

  return {
    id: row.id,
    status: row.status,
    amount: row.amount,
    isEditable,
    createdAt: row.createdAt,
  };
}

对应的返回体类型也补上了字段。

export class OrderVO {
  id: string;
  status: OrderStatus;
  amount: number;
  isEditable: boolean;
  createdAt: Date;
}

这一轮我对它的评价是:定位很准,改动范围也基本可控。

它没有上来重构整个订单模块,也没有自作主张改数据库 schema,这说明前面的“约束”确实起了作用。


四、测试补充:它知道要测什么,但第一次没测全

Agent 给后端补了状态过滤测试。

it.each([
  ['all', 3],
  ['canceled', 1],
  ['draft', 1],
])('should return correct count when status=%s', async (status, expected) => {
  const result = await service.listOrders({ userId: 'u1', status });
  expect(result).toHaveLength(expected);
});

it('should include isEditable in response', async () => {
  const result = await service.listOrders({ userId: 'u1', status: 'all' });
  expect(typeof result[0].isEditable).toBe('boolean');
});

这个测试方向是对的,但还不够。

它验证了数量,也验证了字段存在,却没有进一步验证 isEditable 的业务语义。例如草稿订单应该可编辑,已取消订单是否可编辑,锁定订单是否不可编辑,这些都需要人来判断。

这就是 Agent 做业务需求时最常见的问题:它能补“代码层面的测试”,但不一定能补“业务语义层面的测试”。

如果你只看测试绿了,很容易误以为需求完成了。


五、前端校验:能改,但边界值容易翻车

前端部分,它在表单规则里加入了手机号格式校验。

const rules = {
  phone: [
    { required: true, message: '请输入手机号', trigger: 'blur' },
    {
      validator: (_rule, value, callback) => {
        const ok = /^1[3-9]\d{9}$/.test(String(value || '').trim());
        if (!ok) return callback(new Error('手机号格式不正确'));
        callback();
      },
      trigger: 'blur',
    },
  ],
};

这个实现基本可用,但我后来又让它补了几个边界用例:空值、undefined、带空格的手机号、明显错误的号码、合法号码。

因为表单校验最容易出现一种情况:正常手输没问题,自动化测试或者回填数据一来就炸。

这类问题不是 AI 特有,人写也经常漏。但 Agent 的特点是,它会优先写出“主路径可用”的代码,边界值需要你明确要求它补。


六、第一次跑测试:翻车,但翻得很真实

Agent 执行了相关测试。

pnpm test order.service.spec.ts --runInBand
pnpm vitest run src/views/order/OrderForm.spec.ts

第一次结果并没有全绿。

后端测试报错:

Expected length: 3
Received length: 2

排查后发现,不是业务逻辑错,而是 fixture 里少了一条 canceled 订单。也就是说,测试预期写了 3 条,但测试数据只有 2 条符合场景。

于是它补了测试数据。

export const orders = [
  { id: 'o1', userId: 'u1', status: 'draft', amount: 10 },
  { id: 'o2', userId: 'u1', status: 'canceled', amount: 20 },
  { id: 'o3', userId: 'u1', status: 'paid', amount: 30 },
];

前端测试也报过一次边界问题:

TypeError: Cannot read properties of undefined (reading 'trim')

原因是校验函数里一开始直接对 value 调用了 trim(),但测试里传入了 undefined

修复后变成:

const ok = /^1[3-9]\d{9}$/.test(String(value ?? '').trim());

再次执行测试后,相关用例通过。

Test Suites: 8 passed, 8 total
Tests:       42 passed, 42 total

这一步其实很有价值。

因为真实开发不是“一次生成代码,然后完美运行”。真实开发往往是:写一点,跑一下,报错,定位,再修,再跑。

Agent 的优势就在这里。它不会嫌烦,也不会因为测试报错就开始情绪低落。只要你的测试反馈足够明确,它可以持续迭代。


七、人工 Review:这一步不能省

虽然测试通过了,但我没有直接合并。

我人工 Review 时拦了三个点。

第一个点是 isEditable 的语义。

这个字段不能简单理解成“前端是否展示编辑按钮”。它本质上应该表示“当前订单在业务上是否允许编辑”。前端按钮只是它的一个使用场景。

如果把语义写窄了,后面别的端或者别的业务复用这个字段时,很容易产生误解。

第二个点是过度抽象。

Agent 曾经尝试抽一个类似 buildStatusCondition() 的方法,把一个简单判断包装起来。这个抽象看起来更“工程化”,但在这个场景下没有必要。

原逻辑只有一行判断:

if (query.status && query.status !== 'all') {
  qb.andWhere('o.status = :status', { status: query.status });
}

为了这一行单独抽函数,反而增加阅读成本。

第三个点是测试还缺反例。

比如 status=all 且数据集为空时,应该返回空数组,而不是抛错。这个 case 很简单,但能防止后续有人在转换逻辑里写出不安全代码。

所以最终我让 Agent 继续补了一条测试:

it('should return empty array when status=all and no orders found', async () => {
  mockOrderRepo.findByUserId.mockResolvedValueOnce([]);

  const result = await service.listOrders({ userId: 'u-empty', status: 'all' });

  expect(result).toEqual([]);
});

这里也能看出一个结论:Agent 可以帮你写代码,但不能替你承担业务判断。

尤其是字段语义、边界条件、抽象是否必要,这些还是需要有经验的人把关。


八、最终 PR 摘要

最后生成的 PR 摘要大致如下。

标题:

fix(order): handle status=all correctly and add isEditable in response

改动文件:

src/modules/order/order.service.ts
src/modules/order/dto/order.vo.ts
test/order/order.service.spec.ts
test/fixtures/orders.ts
src/views/order/OrderForm.vue
src/views/order/OrderForm.spec.ts

改动内容:

修复 status=all 时订单列表错误过滤的问题。

订单返回体新增 isEditable: boolean 字段。

补充 allcanceleddraft 以及空数据集场景的后端测试。

前端表单新增手机号格式校验,格式不合法时禁止提交。

补充前端表单校验测试。

风险点:

isEditable 是业务语义字段,未来如果订单可编辑规则变化,应该统一收口在 Service 层维护,避免前后端各写一套判断。

回滚方案:

本次改动不涉及数据库 schema。若上线后发现兼容问题,回滚该 PR 即可恢复旧逻辑。


九、这次实验给我的三个判断

1. Agent 不是代码生成器,而是一个“可管理的初级开发者”

以前我们用 Copilot,更多是让它补函数、补样板代码、解释报错。

但 Agent 的工作方式不一样。它可以连续做多步任务:读需求、找文件、改代码、跑测试、看报错、继续修。

这已经不是简单的代码补全,而是接近一个低阶开发者的工作流。

但前提是,你必须会管理它。

你要告诉它目标是什么,哪些地方不能动,什么结果算完成,哪些地方必须交给人确认。

如果没有这些,它会非常积极地把事情做偏。

2. Agent 最强的不是“写代码”,而是“跑脏活流程”

这次实验里,我觉得最省时间的不是它写了那几行业务代码,而是它帮我做了很多重复性工作。

比如定位相关文件、补测试、根据报错修 fixture、整理 PR 摘要、列风险点和回滚方案。

这些事情人当然也能做,但很碎、很耗耐心。

Agent 的优势恰好在这里:它不会烦,也不会跳步骤。只要你把反馈给清楚,它可以一直迭代。

3. 程序员不会立刻被替代,但工作重心会变化

如果一个程序员的主要价值只是“根据明确需求写几行 CRUD”,那压力确实会越来越大。

因为这部分工作,Agent 已经能做得越来越像样。

但真实项目里,最难的从来不只是写代码,而是判断什么该改、什么不该改,边界在哪里,风险在哪里,怎么验收,出了问题怎么回滚。

这些能力短期内不会消失,反而会变得更重要。

未来程序员更像是带着一组 AI 初级开发者工作的人。

你不一定要亲手写每一行代码,但你必须知道每一行代码为什么应该这样写。


十、我会怎么给 Agent 派工

这次实验之后,我总结了一个比较实用的 Agent 工单模板。

【角色】
你是本仓库的开发者,需要完成一个小型真实工单。

【目标】
用一句话描述最终业务结果,不要直接写实现细节。

【背景】
说明这个需求为什么存在,涉及哪些页面、接口或用户场景。

【约束】
列出不能改什么、必须兼容什么、哪些模块不要动。

【验收标准】
用可测试的条件表达,例如接口返回、边界 case、测试命令、UI 行为。

【执行要求】
先阅读相关代码并给出修改计划。
确认改动范围后再修改代码。
每次修改后运行相关测试。
如果测试失败,先解释原因,再继续修复。

【输出要求】
输出改动文件清单、每个文件的修改理由、风险点、回滚方案、需要人工确认的业务点。

这个模板的核心不是“让 AI 更听话”,而是让任务变得可验收。

对 Agent 来说,模糊需求会放大风险;明确验收会放大收益。


结尾:真正的问题不是 AI 会不会取代你

这次实验之后,我对 AI Agent 的判断更现实了。

它没有强到可以让我放心睡觉、第二天直接合 PR。

但它也没有弱到只能写几个工具函数。

它已经可以处理一部分真实工单,尤其是边界清楚、影响范围可控、测试反馈明确的任务。

所以问题不是“AI Agent 能不能替代程序员”。

更准确的问题是:当你手里多了一个 AI 初级开发者,你会不会拆任务、设边界、写验收、做 Review、控风险?

如果你不会,它可能会制造更多看起来正确的错误。

如果你会,它真的能帮你省下不少时间。

未来的程序员,不只是写代码的人。

更像是能把需求、代码、测试、风险和 AI 工具组织起来的人。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐