人工智能(Artificial Intelligence,AI)编程工具越来越强以后,程序员面临的问题变了。
以前可以说:“复杂业务逻辑 AI 写不好”“并发代码 AI 容易出错”“安全代码还是得人写”。但现在,Claude Code、Codex、Cursor 这类工具已经能处理不少真实工程任务:跨文件修改、状态机实现、异常处理、单元测试补齐、代码审查、接口联调,很多都不再是演示级能力。
所以,程序员的优势不应该建立在“AI 做不了”上。
更准确的说法是:AI 能写很多代码,但它需要人把问题定义清楚、把上下文组织好、把结果验证明白,并对上线后的结果负责。
这才是 Vibe Coding 时代真正有竞争力的工程能力。
Vibe Coding 和 AI 辅助编程不是一回事
Vibe Coding 这个词通常指一种非常松散的开发方式:开发者把需求交给 AI,让 AI 生成代码,然后不仔细理解、不认真审查,直接接受结果。
它和正常的 AI 辅助编程差别很大。
| 维度 | Vibe Coding | AI 辅助编程 |
|---|---|---|
| 需求输入 | 模糊描述,依赖 AI 猜测 | 明确业务规则、边界条件和技术约束 |
| 代码生成 | AI 负责大部分实现 | AI 参与实现,人负责方向和约束 |
| 代码审查 | 粗略扫一眼甚至直接接受 | 按业务语义、安全性、性能、可维护性审查 |
| 报错处理 | 把错误粘给 AI 继续修 | 先定位根因,再决定自己修还是让 AI 辅助 |
| 责任归属 | 容易把问题归因给工具 | 谁接受代码,谁对代码负责 |
真正的问题不在于“有没有用 AI 写代码”,而在于:你是否理解自己接受的代码,以及是否能为它负责。
一段 AI 生成的代码进入代码仓库后,就不再是“AI 的代码”,而是团队系统的一部分。线上出问题时,用户不会关心这段代码是谁生成的,只会关心系统为什么不可用、数据为什么错了、钱为什么退错了。
AI 辅助编程的合理流程应该是这样的:
flowchart LR
A[模糊需求] --> B[拆解业务规则]
B --> C[整理上下文]
C --> D[让 AI 生成候选实现]
D --> E[人工审查代码]
E --> F[补充测试与验证]
F --> G[发布上线]
G --> H[监控与复盘]
E -->|不符合预期| C
F -->|测试失败| D
AI 在这个流程里承担的是“候选实现生成器”和“效率放大器”的角色,不能替代需求澄清、技术判断和结果负责。
程序员的优势不在于手速,而在于工程控制力
如果把 AI 当成一个很强的初级工程师,它的能力很明显:写得快、知识面广、不怕重复、能快速给出多个方案。但它也有明显限制:不知道业务背后的真实规则,不知道团队的历史坑,不知道某个看起来普通的字段其实是财务对账关键字段。
程序员的核心优势可以拆成五类。
flowchart TB
A[程序员的工程控制力] --> B[问题定义]
A --> C[上下文构建]
A --> D[结果验证]
A --> E[技术决策]
A --> F[成本控制]
B --> B1[把一句需求拆成可执行规格]
C --> C1[给 AI 足够但不冗余的信息]
D --> D1[判断代码行为是否符合业务语义]
E --> E1[结合业务阶段和团队约束做取舍]
F --> F1[控制 Token、时间和审查成本]
1. 问题定义能力:把一句话变成可执行规格
“加一个退款功能”不是一个可直接编码的需求。
AI 可以根据这句话写出一个退款接口,但它不知道这些问题的答案:
| 问题 | 为什么重要 |
|---|---|
| 是否支持部分退款 | 影响订单金额、库存、优惠分摊和财务对账 |
| 多次退款如何处理 | 影响幂等性和退款状态流转 |
| 优惠券、积分、余额如何退回 | 影响资金规则和用户权益 |
| 退款失败是否重试 | 影响任务调度和异常补偿 |
| 退款超时如何关闭 | 影响状态机和定时任务 |
| 已发货订单能不能退 | 影响售后规则和履约系统 |
如果这些规则没有定义清楚,AI 生成的代码大概率只是“看起来像退款功能”。它可能有接口、有数据库操作、有状态更新,但关键业务语义不一定对。
一个可执行的退款规格至少要包含这些内容:
需求:创建退款申请接口
输入:
- orderId:订单 ID
- refundAmount:退款金额,单位为分
- reason:退款原因
业务规则:
- 只允许已支付且未全额退款的订单发起退款
- 支持部分退款,但累计退款金额不能超过实付金额
- 同一个 requestId 重复请求必须返回同一结果,不能重复退款
- 使用优惠券支付的订单,退款时优惠券不退回,只退现金部分
- 调用支付渠道失败时,退款单进入 RETRY_PENDING 状态
异常规则:
- 订单不存在:返回 ORDER_NOT_FOUND
- 订单状态不允许退款:返回 ORDER_STATUS_INVALID
- 退款金额非法:返回 REFUND_AMOUNT_INVALID
验收标准:
- 重复请求不会产生多笔退款
- 部分退款后订单保持 PARTIAL_REFUNDED 状态
- 全额退款后订单进入 REFUNDED 状态
当需求被拆到这个粒度,AI 才有可能生成接近可用的代码。否则,AI 只是在补全一个模糊想法。
2. 上下文构建能力:给足信息,但不要把代码库全塞进去
AI 输出质量高度依赖上下文质量。
同样是“实现退款接口”,两种输入会得到完全不同的结果:
| 输入方式 | 可能结果 |
|---|---|
| 只写一句“帮我写退款接口” | AI 根据通用经验生成代码,和当前系统不匹配 |
| 提供订单状态机、退款表结构、支付渠道接口、异常码规范 | AI 能生成更贴近工程现状的实现 |
| 把整个项目都丢给 AI | Token 消耗暴涨,无关代码还可能干扰判断 |
上下文构建不是“给得越多越好”,而是“给得刚好够用”。
一份高质量上下文通常包括:
| 信息类型 | 示例 |
|---|---|
| 业务规则 | 哪些订单可退、金额如何计算、状态如何流转 |
| 数据结构 | 订单表、退款表、关键字段含义 |
| 依赖接口 | 支付渠道退款接口、库存回补接口 |
| 代码规范 | 错误码、日志格式、事务边界、异常处理方式 |
| 边界条件 | 重复请求、超时、失败重试、并发退款 |
| 禁止事项 | 不要改公共接口、不要引入新依赖、不要改变旧字段含义 |
可以把上下文组织成固定模板,减少每次临时拼凑的成本:
## 背景
当前系统已有订单模块和支付模块,需要新增退款申请能力。
## 目标
实现 RefundService#createRefund 方法,并补充必要的单元测试。
## 相关代码
- OrderService:用于查询订单
- RefundRepository:用于保存退款单
- PaymentClient:用于调用支付渠道退款
## 业务规则
1. 只有 PAID、PARTIAL_REFUNDED 状态允许退款
2. 累计退款金额不能超过订单实付金额
3. requestId 必须保证幂等
## 技术约束
1. 不修改现有数据库字段
2. 不引入新的第三方库
3. 保持当前异常码风格
4. 事务只包裹本地数据库操作,不包裹外部支付调用
## 输出要求
1. 给出核心实现代码
2. 给出单元测试用例
3. 标出你认为需要人工确认的业务点
这类模板的价值不只是让 AI 更容易理解任务,也方便团队沉淀使用规范。
3. 结果验证能力:代码能跑,不代表业务是对的
AI 生成的代码最危险的地方不是语法错误,而是“合理但错误”。
这种代码通常具备几个特征:
- 命名看起来正确;
- 流程看起来完整;
- 单元测试可能也能通过;
- 但业务语义和真实需求不一致。
例如退款场景里,下面几类问题都不一定能靠简单测试发现:
| 问题 | 表面现象 | 实际风险 |
|---|---|---|
| 退到了错误账户 | 接口返回成功 | 资金损失 |
| 幂等键用错字段 | 单次测试正常 | 重复请求产生多笔退款 |
| 金额用浮点数计算 | 小额测试正常 | 对账出现精度误差 |
| 权限校验只查登录态 | 普通用户可退别人的订单 | 严重越权 |
| 支付调用放进数据库事务 | 本地锁占用过久 | 高并发下性能下降 |
审查 AI 代码时,不能只看“有没有编译错误”,还要检查三层含义:
flowchart TB
A[AI 生成代码审查] --> B[业务语义]
A --> C[安全风险]
A --> D[工程质量]
B --> B1[状态流转是否正确]
B --> B2[金额、对象、权限是否符合业务规则]
B --> B3[幂等和补偿是否完整]
C --> C1[输入校验]
C --> C2[权限控制]
C --> C3[敏感信息处理]
D --> D1[事务边界]
D --> D2[性能风险]
D --> D3[代码风格和可维护性]
测试也要围绕业务语义设计,而不是只覆盖主流程。
@Test
void shouldNotCreateDuplicateRefundWhenRequestIdRepeated() {
// given
String requestId = "refund-req-001";
RefundCommand command = new RefundCommand(orderId, requestId, 1000);
// when
RefundResult first = refundService.createRefund(command);
RefundResult second = refundService.createRefund(command);
// then
assertEquals(first.getRefundId(), second.getRefundId());
assertEquals(1, refundRepository.countByRequestId(requestId));
}
这类测试表达的是业务约束:同一个幂等键只能产生一笔退款。AI 可以帮忙写测试,但测试目标必须由理解业务的人来定义。
4. 技术决策能力:AI 能列方案,不能替你拍板
AI 很擅长列出方案对比。例如“用 Redis 还是本地缓存”“用同步调用还是消息队列”“拆微服务还是保持单体”,它可以快速给出优缺点。
但真实技术决策不只看技术优点,还要看具体约束:
| 决策因素 | AI 不一定知道的信息 |
|---|---|
| 团队能力 | 团队是否有维护消息队列、缓存集群、分布式事务的经验 |
| 历史事故 | 过去是否因为缓存不一致、消息堆积、接口超时出过问题 |
| 业务阶段 | 当前更需要快速交付,还是更需要长期稳定 |
| 运维成本 | 是否有人值守、是否有监控、是否有回滚方案 |
| 合规要求 | 数据能否上传外部服务,日志是否能记录敏感字段 |
例如,AI 可能建议“高并发读场景可以使用缓存”。这个建议本身没错,但如果团队过去因为缓存穿透和缓存不一致出过线上事故,那么新的缓存方案就必须同时设计降级、限流、空值缓存、监控告警和数据修复脚本。
技术决策可以用简单的架构决策记录沉淀下来:
# ADR-012:退款查询接口是否引入 Redis 缓存
## 背景
退款查询接口 QPS 上升,数据库读压力增加。
## 备选方案
1. 直接优化 SQL 和索引
2. 引入 Redis 缓存
3. 增加只读副本
## 决策
短期先优化 SQL 和索引,不引入 Redis。
## 原因
1. 当前慢查询主要来自缺失组合索引,缓存不是根因
2. 退款状态一致性要求高,缓存会增加一致性维护成本
3. 团队近期缺少缓存一致性监控
## 后续观察指标
- 接口 P95 延迟
- 数据库 CPU
- 慢查询数量
AI 可以辅助生成这份记录,但真正的取舍来自工程上下文。
5. 成本控制能力:AI 编程也有工程成本
Token 是大模型处理文本时使用的基本单位,输入的代码、提示词、历史对话、模型输出都会消耗 Token。很多 AI 编程工具按 Token 或请求量计费,长上下文、多轮对话、反复生成都会抬高成本。
AI 编程成本不只是账单,还包括:
| 成本类型 | 说明 |
|---|---|
| Token 成本 | 输入、输出、缓存写入、工具调用带来的费用 |
| 时间成本 | 等待模型生成、多轮修改、人工审查 |
| 认知成本 | 理解 AI 改了什么,判断是否符合系统约束 |
| 风险成本 | 错误代码进入仓库后带来的排查和回滚成本 |
所以,AI 编程不是“能用就用”,而是要判断综合成本。
Token 成本控制的五个策略
Token 成本控制的目标不是一味省钱,而是在质量、速度和费用之间找到合适平衡。复杂重构用强模型是合理的,一行配置修改却让 AI 扫完整个项目,就很浪费。
策略一:模型路由,不同任务用不同级别模型
不是所有任务都需要最强模型。简单补全、格式调整、注释生成通常不需要深度推理;架构设计、跨模块重构、复杂缺陷分析才更依赖强模型。
| 任务类型 | 推荐模型级别 | 原因 |
|---|---|---|
| 代码补全、变量改名、格式调整 | 小模型 | 任务模式固定,低成本即可完成 |
| 单个函数实现、常规缺陷修复 | 中模型 | 需要理解局部上下文 |
| 跨文件修改、状态机、复杂重构 | 大模型 | 需要更强推理和更长上下文 |
| 代码解释、文档生成 | 小模型或中模型 | 主要是归纳和表达 |
| 安全审查、架构评审 | 大模型 + 人工复核 | 错误代价高,需要深度分析 |
模型路由可以做成一条简单规则链:
flowchart TD
A[开发任务] --> B{是否涉及跨模块影响}
B -->|是| E[大模型]
B -->|否| C{是否需要业务推理}
C -->|是| D[中模型]
C -->|否| F[小模型]
E --> G[人工严格审查]
D --> H[常规审查]
F --> I[快速确认]
不同模型的价格差距可能很大。大量日常任务如果都交给最高级模型,账单会很快膨胀;反过来,复杂任务强行用小模型,可能产生更多返工和审查成本。
策略二:上下文管理,只给相关信息
上下文窗口是 Token 消耗的大头。把整个项目交给 AI,让它“自己找”,看起来省事,实际会带来两个问题:
- 无关代码增加 Token 消耗;
- 噪音信息干扰模型注意力,生成质量可能下降。
更合理的做法是按需组织上下文。
| 做法 | 说明 |
|---|---|
| 只给相关文件 | 修改用户模块时,只提供用户模块及其直接依赖 |
| 用摘要代替全文 | 长配置文件、长接口文档可以先给摘要 |
| 分阶段加载 | 先给接口和业务规则,需要实现细节时再补充文件 |
| 清理历史对话 | 长会话积累大量旧上下文,任务切换时应开启新会话 |
| 排除敏感目录 | 密钥、核心算法、生产配置不进入 AI 上下文 |
可以把上下文分成三层:
flowchart LR
A[任务目标] --> B[必要上下文]
B --> C[可选上下文]
C --> D[禁止上下文]
B --> B1[业务规则]
B --> B2[相关代码]
B --> B3[接口定义]
C --> C1[相似实现]
C --> C2[历史设计说明]
D --> D1[密钥]
D --> D2[生产配置]
D --> D3[无关模块源码]
高质量上下文通常比大上下文更重要。给 AI 一堆无关文件,不如给它一份准确的业务规则和两三个关键代码片段。
策略三:Prompt 写清楚,减少反复来回
模糊 Prompt 最大的问题是会制造多轮返工。
低质量输入通常是这样:
帮我写一个退款接口。
AI 会自己猜技术栈、参数、状态、异常、权限、幂等规则。生成结果不符合预期后,又要继续补充条件、重新生成、继续审查,Token 和时间都会被消耗掉。
更好的输入是这样:
请在现有 Spring Boot 项目中实现退款申请功能。
目标:
- 新增 RefundService#createRefund(RefundCommand command)
- 不需要写 Controller,只写 Service 和单元测试
业务规则:
- 只有 PAID 和 PARTIAL_REFUNDED 订单允许退款
- 累计退款金额不能超过订单实付金额
- requestId 用于幂等,同一个 requestId 重复调用返回同一退款单
- 支付渠道调用失败时,退款单状态为 RETRY_PENDING
技术约束:
- 使用现有 RefundRepository,不新增依赖
- 本地数据库操作需要事务
- 外部支付调用不能放在数据库事务内
- 使用项目已有 BusinessException 和 ErrorCode
输出:
1. RefundService 核心代码
2. RefundServiceTest 单元测试
3. 标出需要人工确认的业务点
好的 Prompt 不只是“说得更详细”,而是减少模型猜测空间。模型猜得越少,返工越少,审查也更容易。
策略四:缓存和复用,不要每次从零开始
很多团队的业务代码有大量重复模式,例如增删改查(Create、Read、Update、Delete,CRUD)、参数校验、分页查询、权限检查、异常封装、日志格式。每次都让 AI 从零生成,既浪费 Token,也容易生成风格不一致的代码。
可以复用三类资产:
| 复用方式 | 适合场景 |
|---|---|
| Prompt 模板 | 固定代码风格、固定输出结构、固定审查要求 |
| 代码模板 | CRUD、表单校验、消息消费、定时任务 |
| 上下文摘要 | 模块职责、核心数据结构、接口约定 |
例如团队可以维护一个标准 CRUD 模板:
## CRUD 生成规范
请基于已有代码风格生成资源管理接口,要求:
1. Controller 只做参数接收和响应封装
2. Service 负责业务校验
3. Repository 负责数据访问
4. 所有列表接口必须分页
5. 创建和更新接口必须校验字段合法性
6. 删除接口使用逻辑删除
7. 补充单元测试,覆盖正常、参数非法、资源不存在三类场景
如果使用支持 Prompt Caching 的模型服务,相同前缀还可以命中缓存,减少重复计算成本。即使不直接使用缓存能力,模板化也能减少沟通成本和审查成本。
策略五:判断任务是否真的适合交给 AI
有些任务让 AI 做更贵。
例如线上出现一个 NullPointerException,日志已经明确指向 OrderService.java 第 127 行,原因是 user.getAddress() 没有判空。熟悉代码的人手动加一行空值判断,可能几十秒就能完成。
如果交给 AI,流程可能变成:
sequenceDiagram
participant Dev as 开发者
participant AI as AI 工具
participant Repo as 代码仓库
Dev->>AI: 粘贴异常日志
AI->>Repo: 读取相关文件
AI->>AI: 分析调用链路
AI-->>Dev: 生成修复方案
Dev->>Dev: 审查是否改对位置
Dev->>Repo: 应用修改
这个流程会消耗 Token,还可能引入额外修改。对于已经定位清楚、修改点明确的小问题,直接手写往往更快、更便宜。
可以用一张表判断是否适合交给 AI:
| 场景 | 更适合 AI | 更适合手写 |
|---|---|---|
| 大量样板代码 | 是 | 否 |
| 多方案探索 | 是 | 否 |
| 不熟悉框架的入门代码 | 是 | 否 |
| 已定位的一行修复 | 否 | 是 |
| 业务风险极高的核心逻辑 | 可辅助,但必须严审 | 是 |
| 已有模板复制即可完成 | 否 | 是 |
判断标准不是“AI 能不能做”,而是:AI 做完之后的生成、等待、审查、返工成本,是否低于自己完成的成本。
AI 生成代码的团队治理
个人使用 AI 写代码,只要自己负责即可;团队规模变大后,必须有明确规范,否则代码质量和安全边界会失控。
代码归属:谁接受,谁负责
AI 生成的代码不能成为责任真空区。一个简单规则足够清晰:
谁把代码提交进仓库,谁负责理解、审查和维护。
这意味着开发者不能用“这是 AI 写的”解释线上问题。AI 可以参与生成,但提交者必须能解释代码行为、边界条件和失败处理。
审查流程:AI 代码走同样的 Review
AI 代码不应该绕过正常 Code Review。相反,部分 AI 代码更需要重点审查,因为它可能写出“看起来很合理但业务不对”的实现。
审查清单可以固定下来:
| 审查项 | 关注点 |
|---|---|
| 业务语义 | 金额、状态、对象、权限是否符合规则 |
| 安全风险 | 输入校验、越权访问、敏感数据泄露 |
| 幂等处理 | 重复请求、重试、并发是否安全 |
| 事务边界 | 本地事务和外部调用是否混在一起 |
| 性能风险 | 是否出现 N+1 查询、全表扫描、大对象加载 |
| 可维护性 | 是否符合项目分层、命名和异常规范 |
| 测试覆盖 | 是否覆盖主流程、异常流程和边界条件 |
N+1 查询指的是先查询一批主数据,再对每条主数据单独查询关联数据,最终产生大量数据库请求。AI 生成列表查询时很容易写出这类代码,需要特别检查。
线上事故处理:先恢复服务,再分析 AI 使用流程
如果 AI 生成的代码导致线上问题,处理顺序不应该因为“代码来自 AI”而变化。
flowchart LR
A[线上异常] --> B[止血]
B --> C[定位影响范围]
C --> D[追踪根因]
D --> E[修复发布]
E --> F[补测试]
F --> G[调整 AI 使用规范]
关键动作包括:
- 止血:回滚、降级、关闭开关,先恢复用户侧服务。
- 定位影响范围:查看监控、日志、链路追踪和数据异常范围。
- 追踪根因:确认是需求理解错误、上下文缺失、审查遗漏,还是测试覆盖不足。
- 补流程:增加测试用例、更新审查清单、限制某类任务使用 AI 自动生成。
一次故障不可怕,真正危险的是同类问题反复发生。
代码安全:不要把敏感信息交给外部模型
AI 编程工具通常需要读取本地代码,并把上下文发送到模型服务端推理。即使供应商承诺不会用于训练,企业仍然需要考虑合规和数据安全。
常见做法包括:
| 措施 | 说明 |
|---|---|
| 使用企业版 | 获得数据隔离、权限管理和审计能力 |
| 敏感项目禁用外部 AI | 核心算法、密钥管理、交易策略等不上传 |
| 配置忽略文件 | 防止工具读取密钥、配置、生产脚本 |
| 制定团队规范 | 明确哪些项目可用 AI,哪些禁止 |
| 保留审计记录 | 方便追踪谁在什么任务中使用了 AI |
例如可以维护 .claudeignore 或类似配置:
# 密钥和证书
*.pem
*.key
*.p12
.env
.env.*
# 生产配置
config/prod/
deploy/secrets/
# 核心敏感模块
src/main/java/com/example/risk/core/
src/main/java/com/example/payment/secret/
# 大文件和无关产物
target/
build/
logs/
这类配置的目的不是降低 AI 能力,而是给 AI 设置边界。能用 AI 的地方充分用,不能上传的内容必须挡住。
面试中更稳的回答方式
当被问到“AI 越来越强,程序员的优势是什么”,不要把回答建立在“AI 写不了复杂业务”上。这个说法很容易被反问:如果给足上下文呢?如果模型再强一点呢?
更稳的回答可以围绕工程闭环:
AI 的代码生成能力已经很强,我不会把优势定义成比 AI 手写更快。我的优势在于控制 AI 编程的整个工程过程:把模糊需求拆成明确规格,给模型组织高质量上下文,审查生成代码的业务语义和安全风险,结合团队现状做技术决策,并控制 Token、审查和返工成本。AI 可以生成代码,但我负责让代码正确、可维护、可上线。
如果继续追问“哪些代码让 AI 写,哪些自己写”,判断标准可以这样表达:
| 判断维度 | 处理方式 |
|---|---|
| 代码模式固定、风险低 | 让 AI 生成,提高效率 |
| 需要多方案探索 | 让 AI 辅助分析,再人工决策 |
| 修改点明确且很小 | 直接手写 |
| 核心资金、安全、权限逻辑 | AI 可辅助,但必须人工严格审查 |
| 涉及敏感代码 | 不上传外部 AI 工具 |
如果追问“AI 代码出问题怎么办”,回答重点是责任和流程:
不把问题归因给工具。先回滚或降级恢复服务,再通过监控、日志和链路追踪定位根因。确认问题后补测试、补审查规则,并调整 AI 使用边界。谁接受代码谁负责,AI 只是生成工具,不能替代工程责任。
真正的优势是让 AI 写得更可靠
Vibe Coding 的风险不在于使用 AI,而在于放弃理解和责任。
AI 编程越强,程序员越需要把精力从“逐行敲代码”转到“定义问题、组织上下文、验证结果、做取舍、控成本”上。手写能力仍然重要,因为 AI 修不了、线上等不了、复杂事故需要人接手;但更关键的是工程控制力。
一句话概括:
程序员的优势不是比 AI 更会补全代码,而是能让 AI 生成的代码更符合业务、更安全、更省成本,并且能为最终结果负责。