本文首发地址 https://h89.cn/archives/576.html

AI 编程最容易让人上头的一点,就是它真的快。

一个下午写完原本要做三天的需求,一个人顶过去一个小组的产出,PR 一开就是上千行,屏幕上全是绿色。那种感觉很容易让人误以为,软件开发终于进入了一个只要"多生成一点"就能持续提效的阶段。说真的,第一次用 Claude Code 把一整个模块的 CRUD 跑通的时候,我自己也觉得——这也太爽了。

但很多团队最近开始慢慢回过味来:代码确实变多了,人却没有更轻松。

Review 队列更长了,线上风险更多了,安全审计更吃力了,真正理解系统的人反而更焦虑了。代码产出像开了闸,审查、验证和兜底能力却没跟着一起扩容。表面上看是效率提升,往里看更像是工程压力整体后移。

《纽约时报》最近发布的一篇报道《The Big Bang: A.I. Has Created a Code Overload》[1],就抓住了这个越来越明显的矛盾:AI 把代码产出推高了,治理能力却没有同步跟上。

另一组数据也能说明这种拧巴。Stack Overflow 2025 Developer Survey 显示,84% 的受访者表示正在使用或计划使用 AI 工具参与开发流程;在职业开发者中,51% 表示每天使用。与此同时,开发者对 AI 输出准确性的信任并不高:46% 表示不信任,33% 表示信任,只有 3% 表示"高度信任" [2]。

说白了,大家已经离不开它了,但真要把这些代码推上线,心里又都没那么踏实。

所以这篇文章不想再讲“AI 编程有多快”。这件事已经不是新闻了。

更值得讲的是另一面:当代码产出快到超过团队的阅读、审查和兜底能力,工程代价会怎么一点点冒出来。

先看根子:顺风顺水路径陷阱

AI 生成代码的常见问题,不是完全不会处理复杂场景,而是在没有明确约束时,往往先给你一版顺风顺水路径。

所谓顺风顺水路径,就是一切条件都刚刚好的执行路径:网络始终可用、数据库始终秒回、用户输入永远合法、第三方 API 永远不超时也不限流。

这不是 AI 笨。模型虽然见过大量开源代码,但训练数据并不会天然标注哪些实现真正经历过高并发、脏数据、缓存雪崩和线上事故的拷打。那些真正能扛事的工程细节,往往不在最容易被学到的那层表面代码里。

它当然能写出看起来很像那么回事的代码,但真到出血的时候,常常不知道先捂哪儿。这个落差,才最麻烦。

例子一:缓存击穿

让 Claude Code 写一个加 Redis 缓存的商品查询接口,它会给你这种代码:

AI 生成(顺风顺水路径):

@RestController
public class ProductController {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ProductRepository productRepository;

    @GetMapping("/api/hot-products")
    public List<Product> getHotProducts() {
        String cached = redisTemplate.opsForValue().get("hot_products");
        if (cached != null) {
            return JSON.parseArray(cached, Product.class);
        }
        List<Product> products = productRepository.findTop20ByOrderBySalesDesc();
        redisTemplate.opsForValue().set("hot_products",
            JSON.toJSONString(products), 1, TimeUnit.HOURS);
        return products;
    }
}

逻辑清晰,本地跑也没问题。真上线上压一压,就容易出事。我第一次看到这段代码的时候还想,这不是挺好的嘛;直到在压测环境亲眼看着连接池被打满,才明白问题出在哪。

原因:缓存过期瞬间,高并发场景下(假设 QPS 10000),这 10000 个请求同时发现 cached 为空,同时打到数据库,连接池秒空。

下面的代码只是示意,用来说明为什么生产环境要额外处理并发和回源问题,不是可以直接照抄上线的完整方案。

防御性写法(示意):

@GetMapping("/api/hot-products")
public List<Product> getHotProducts() {
    String cached = redisTemplate.opsForValue().get("hot_products");
    if (cached != null) {
        return JSON.parseArray(cached, Product.class);
    }

    String lockKey = "lock:hot_products";
    boolean locked = false;
    try {
        // 分布式锁,NX + EX,防止缓存击穿
        locked = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
        if (!locked) {
            // 没拿到锁,短暂等待后重试(读旧缓存或等别人回填)
            Thread.sleep(50);
            cached = redisTemplate.opsForValue().get("hot_products");
            if (cached != null) {
                return JSON.parseArray(cached, Product.class);
            }
            return productRepository.findTop20ByOrderBySalesDesc();
        }

        // 双重检查:拿到锁后再查一次缓存
        cached = redisTemplate.opsForValue().get("hot_products");
        if (cached != null) {
            return JSON.parseArray(cached, Product.class);
        }

        List<Product> products = productRepository.findTop20ByOrderBySalesDesc();
        redisTemplate.opsForValue()
            .set("hot_products", JSON.toJSONString(products), 1, TimeUnit.HOURS);
        return products;
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return productRepository.findTop20ByOrderBySalesDesc();
    } finally {
        if (locked) {
            redisTemplate.delete(lockKey);
        }
    }
}

复杂度直接提升一个量级:分布式锁、锁过期、try/finally、双重检查、退避策略,全都进来了。更关键的是,这里就算用了锁也还没完,生产上通常还要处理锁值校验、过期后误删、旧缓存兜底或 singleflight 等细节。这就是生产代码和 Demo 代码的区别:前者长这样,不是因为它喜欢复杂,而是被事故逼的。

这正是问题所在。AI 很擅长先把功能拼出来,却不一定会主动替你补上这些让系统真正扛得住流量的约束。

例子二:支付接口为什么会重复扣款

写一个扣减余额的接口:

AI 生成(顺风顺水路径):

@Service
public class PaymentService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void deductBalance(Long userId, BigDecimal amount) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new BusinessException("用户不存在"));
        if (user.getBalance().compareTo(amount) < 0) {
            throw new BusinessException("余额不足");
        }
        user.setBalance(user.getBalance().subtract(amount));
        userRepository.save(user);
    }
}

本地测试通常也很好看。可一到生产环境,用户网络一抖,连点 3 次支付按钮,3 个并发请求同时查到余额 100,同时执行扣减,最后就会出现用户 100 块买走 300 块东西这种很难看的场面。这种 bug 我见过不止一次,查到最后几乎都是同一个原因:代码能跑,但没人想过“同时跑”会怎样。

问题出在哪?@Transactional 只保证了方法内事务,没加行锁,3 个事务可以并行读到同一个 balance 值。

下面的代码同样只是示意。真正落地时,幂等键、唯一索引和账户一致性控制都要一起设计,少一个都可能出事。

防御性写法(示意):

@Service
public class PaymentService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PaymentLogRepository paymentLogRepository;

    @Transactional
    public void deductBalance(Long userId, BigDecimal amount, String transactionId) {
        // 幂等性校验:同一笔交易不重复扣款
        if (paymentLogRepository.existsByTransactionId(transactionId)) {
            log.info("重复请求,跳过: {}", transactionId);
            return;
        }

        // 悲观锁:SELECT ... FOR UPDATE,保证串行
        User user = userRepository.findByIdForUpdate(userId)
            .orElseThrow(() -> new BusinessException("用户不存在"));
        if (user.getBalance().compareTo(amount) < 0) {
            throw new BusinessException("余额不足");
        }
        user.setBalance(user.getBalance().subtract(amount));
        userRepository.save(user);

        // 记录交易流水,支撑幂等
        paymentLogRepository.save(PaymentLog.builder()
            .transactionId(transactionId)
            .userId(userId)
            .amount(amount)
            .build());
    }
}

三个关键改动:幂等键、串行化控制、交易流水记录。再往前走一步,生产里通常还会给 transactionId 加唯一约束,避免 exists + save 这种先查后写留下竞态窗口。

问题不在于 AI 完全想不到这些,而在于如果没人明确约束,它通常不会优先替你把这些坑填平。

更大的代价:Code Review 开始崩

代码产出翻 10 倍,Review 速度没变。真正的矛盾就在这里。

一个初级程序员用 Cursor 随手一划拉,提交一个 3000 行、横跨 15 个文件的 PR。Reviewer 打开 GitHub,看到满屏绿色,第一反应往往不是兴奋,而是头大。人的大脑在短时间里根本建立不了 3000 行代码的全局上下文。

结果就是 LGTM 综合症:扫两眼,没语法错误,CI 测试也过了,于是直接 Approve。可问题是,测试本身也可能主要覆盖顺风顺水路径。看着 3000 行的 PR,很多人心里想的都是:“算了,先过吧,有问题再说。”然后问题就真的来了。

技术债开始滚雪球。

更麻烦的是,很多团队会误把“代码变多了”当成“交付变快了”。

AI 最容易优化的是“开始写”的速度,但软件工程真正昂贵的部分,往往不在把代码敲出来的那几小时里,而在理解上下文、补齐边界、验证行为、定位问题,以及后续长期维护的成本里。

代码产量上去了,不代表系统就更容易演进;很多时候,团队只是把原本分散在设计、Review、联调和线上兜底阶段的压力,统一推迟到了后面。

站在团队管理的角度,这种后移尤其伤。资深工程师本来应该花时间做架构、拆复杂问题、带新人,结果越来越多的精力被大体量 PR 和风险排查吃掉。表面上看,团队每个人都“写得更多了”;实际上,真正稀缺的判断力反而被 Review 和救火不断抽干。

这也是为什么很多团队会出现一种别扭的状态:新人提交速度更快了,仓库里的代码也明显变多了,但真实吞吐并没有同步上涨,核心成员反而更累。

还有一层代价,经常在项目跑了一阵子后才显出来:可观测性没跟上。AI 很会补业务逻辑,却不一定会顺手把日志、指标、链路追踪、报警阈值这些非功能性部分一起补齐。结果不是 bug 单纯变多,而是问题一旦出现,团队更难定位、更难回溯,也更难判断影响范围。

更隐蔽的问题:幻觉依赖与供应链投毒

如果说 Review 崩溃还算显眼,那安全问题往往更隐蔽。AI 会"捏造"不存在的第三方库。

处理冷门视频格式转换,AI 可能给你写 import com.example:video-converter-pro:1.0,看起来很合理。你顺手加到 pom.xml 或执行 pip install video_converter_pro——但 Maven Central 或 PyPI 上根本没有这个包。

攻击者早就盯上这个漏洞:大量收集 AI 幻觉出来的包名,抢先注册,植入挖矿木马或窃密脚本。你的 CI 一拉依赖,构建服务器就可能被顺手接管。更糟的是,你可能连自己是什么时候中招的都不知道。

这不是传统供应链攻击的替代品,而是 AI 把旧问题放大后出现的新风险形态。最近这类问题常被称为 slopsquatting:模型先幻觉出一个不存在的包名,攻击者再抢先注册,等着别人自己把门打开 [3]。

怎么兜底:三种更现实的做法

1. 先让 AI 审 AI

既然代码是 AI 大规模生成出来的,审计层也应该部分自动化。在 CI/CD 流水线里加一个审计 Agent,是值得认真考虑的方向:

# .github/workflows/ai-code-review.yml
name: AI Code Audit
on:
  pull_request:
    types: [opened, synchronize]

jobs:
  ai-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get PR diff
        id: diff
        run: |
          DIFF=$(git diff origin/main...HEAD)
          echo "diff<<EOF" >> $GITHUB_OUTPUT
          echo "$DIFF" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: AI Security Audit
        run: |
          curl -s https://api.anthropic.com/v1/messages \
            -H "x-api-key: ${{ secrets.CLAUDE_API_KEY }}" \
            -H "content-type: application/json" \
            -H "anthropic-version: 2023-06-01" \
            -d '{
              "model": "claude-sonnet-4-20250514",
              "max_tokens": 4096,
              "system": "你是一个极其严苛的资深安全架构师。审查以下 PR diff,从 5 个维度攻击性审查:1) 并发与竞态条件 2) 资源耗尽/内存泄漏 3) 缓存击穿/穿透/雪崩 4) 幂等性 5) 安全漏洞与幻觉依赖。发现问题直接 REJECT 并给出修复建议。没问题才 APPROVE。",
              "messages": [{"role": "user", "content": "'"${{ steps.diff.outputs.diff }}"'"}]
            }'

这类方案的价值,不是让 AI 代替人拍板,而是先把明显的并发漏洞、资源风险和幻觉依赖筛掉,别让人工 Review 一上来就淹死在噪音里。

当然,真到生产落地,还得补上脱敏、分片、失败门禁和合规边界。自动化审计能挡住一部分低级错误,但挡不住所有侥幸心理。

2. 把爆炸半径收小

单体不是原罪,但如果系统本来就边界模糊、资源混用、发布回滚粗糙,AI 只会把这些问题放大得更快。它在某个角落写个死循环,最后可能拖垮的不是一个接口,而是一整片链路。

更稳的做法,是先把爆炸半径控制到最小:

graph TB subgraph "防爆门架构" GW[API Gateway<br/>统一入口 + 限流] subgraph "核心交易链路(人写)" CORE[Order Service<br/>严格审查] PAY[Payment Service<br/>严格审查] end subgraph "AI 生成模块(隔离区)" REC[Recommendation Service<br/>独立容器<br/>CPU: 2核 MEM: 1G] LOG[Log Analytics Service<br/>独立容器<br/>CPU: 1核 MEM: 512M] end subgraph "数据层" DB_CORE[(核心库<br/>仅核心服务访问)] DB_REC[(推荐库<br/>仅推荐服务访问)] end GW --> CORE GW --> PAY GW --> REC GW --> LOG CORE --> DB_CORE PAY --> DB_CORE REC --> DB_REC end style CORE fill:#07c160,stroke:#10ad0c style PAY fill:#07c160,stroke:#10ad0c style REC fill:#a8d5ba,stroke:#c8e6cc style LOG fill:#a8d5ba,stroke:#c8e6cc

三个原则:

  • 严格 API 契约:不管内部是人写还是 AI 写,模块间通信走 gRPC/OpenAPI,契约不能动
  • 资源隔离:AI 模块跑独立容器/K8s Pod,配 ResourceQuota,OOM 只重启自己
  • 数据库权限最小化:只能访问专属 schema,不给全局库权限

接口不变,内部实现才有条件反复调整。这样即使 AI 写崩了一个隔离区模块,也不至于把核心交易链路一起带下水。

3. 把测试重新拿回人手里

业务代码可以让 AI 帮你写,但测试约束最好别一起外包。边界条件、异常状态、并发场景,还是得由人先想明白。

流程要翻转:先手写测试用例(覆盖边界条件、异常状态、并发场景),再让 AI 实现业务逻辑直到测试全过。

graph LR A1[人类写需求] --> A2[AI 写代码] --> A3[AI 写测试] --> A4[全部通过] A4 -.-> A5[上线后出问题] style A5 fill:#ff6b6b,stroke:#ee5a5a
graph LR B1[人类写需求] --> B2[人类写测试用例<br/>覆盖边界/并发/异常] --> B3[AI 写代码<br/>直到测试全过] --> B4[人工 Review] style B2 fill:#a8d5ba,stroke:#c8e6cc

测试用例就是给 AI 戴上的缰绳。它不保证内部实现优雅,但至少能让外部行为别轻易跑偏。这事听起来不浪漫,真出事故时却往往最实在。说白了,测试代码才是你的底线。

最后要分清:哪些代码适合交给 AI

真正现实的做法,不是“全交给 AI”,也不是“坚决不用 AI”,而是先把代码分层。

有些代码天生更适合让 AI 提效:边界清晰、风险可控、出错后容易替换的部分,比如后台管理页、数据转换脚本、样板接口、测试桩、内部工具。这类代码更接近“局部实现题”,AI 往往能帮你省掉不少重复劳动。

但有些代码,天然就不适合放手让它自由生成:强状态、强一致性、高耦合、跨系统事务、涉及资金、安全、权限、审计、合规的链路。这些地方真正值钱的不是把代码拼出来,而是做对取舍、守住边界、明确失败语义。

换句话说,判断标准不该是“这段代码难不难写”,而该是“这段代码一旦写错,代价由谁承担,能不能快速发现,出了问题能不能安全回滚”。用一句糙话讲:这段代码炸了,你会不会半夜被电话吵醒?如果会,那就别偷懒。

如果这三个问题的答案都不轻松,那就别把它当成普通的生成任务。

如果现在就要做,先落这 4 件事

  • 限制 PR 大小和变更范围,避免一次性把上下文压垮
  • 核心链路要求人工先写关键测试用例,再让 AI 参与实现
  • AI 生成代码默认提高 Review 等级,重点查并发、幂等、资源释放和异常路径
  • 新增依赖必须校验来源、仓库可信度和包名真实性,别让幻觉依赖直接进入 CI

参考


AI 把代码生成的边际成本一把拉低之后,团队真正稀缺的能力就会往别处移动:架构设计、逻辑审查、复杂度控制,以及出问题时能不能稳住局面。

说得再直白一点,未来真正拉开差距的,未必是谁写得更快,而是谁更能判断什么适合交给 AI,什么必须自己扛,什么上线前一定要死磕到底。出了事谁来兜底,才是真本事。

工程师当然还是要写代码,但只会写代码,恐怕越来越不够了。你还得会收边界、定约束、做审查、控风险。AI 让写代码这件事变快了,却也把工程判断的重要性重新抬回了台面上。


本文链接:AI 把代码产量拉爆之后,为什么团队反而更累了? - https://h89.cn/archives/576.html

版权声明:原创文章 遵循 CC 4.0 BY-SA 版权协议,转载请附上原文链接和本声明。

标签: AI, 屎山

添加新评论