AI在代码中埋的三颗雷,快要爆了!

Author: 躺平的程序员 | Origin link: wechat link

0 1
完美代码


张大胖一上班,就看到了小梁和小王昨天晚上发来的两个Code Review请求。


张大胖先打开了小梁的代码,这是要并发调用三个服务(用户资料、最近订单和忠诚度状态), 然后把结果汇集起来,用于在用户仪表盘中展示。


张大胖快速看了一下代码:写得挺漂亮的嘛!


@Servicepublic class DashboardService {    private final ProfileServiceClient profileClient;    private final OrderServiceClient orderClient;    private final LoyaltyServiceClient loyaltyClient;       public UserDashboard aggregateDashboardData(UUID userId) throws ExecutionExceptionInterruptedException {        CompletableFuture<Profile> profileFuture = CompletableFuture.supplyAsync(() -> profileClient.fetchUserProfile(userId));        CompletableFuture<List<Order>> ordersFuture = CompletableFuture.supplyAsync(() -> orderClient.fetchRecentOrders(userId));        CompletableFuture<LoyaltyStatus> loyaltyFuture = CompletableFuture.supplyAsync(() -> loyaltyClient.fetchLoyaltyStatus(userId));        CompletableFuture.allOf(profileFuture, ordersFuture, loyaltyFuture).join();        return new UserDashboard(            profileFuture.get(),            ordersFuture.get(),            loyaltyFuture.get()        );    }}


利用了现代Java的特性,使用 CompletableFuture 协调异步调用。


张大胖又打开了小王的代码,processMessage 并行调用两个外部 API(Promise.all),然后依赖返回结果更新数据库。


//简化版代码async function processMessage(message) {  const [result1, result2] = await Promise.all([    callExternalApi1(message.data),    callExternalApi2(message.relatedId)  ]);  await updateDatabase({ result1, result2, message });  }


代码写得也很漂亮,张大胖上午忙得四脚朝天,匆匆地看了一眼,就Approve了。


不久以后,经过功能测试的代码进入了CI流水线,然后就进入了生产系统。


闲下来的张大胖到茶水间倒水,猛然间想起了小梁和小王的代码: 这不对啊,我们团队写代码不是这样的啊!


代码也太干净了,就 像教科书上的代码,或者网上博客的示例代码


仔细琢磨一下,代码中其实埋了三个雷:


地雷1:线程问题


小梁的代码用的supplyAsync(),但是却没有指定自己的线程池,这意味着所有的任务都跑在默认的ForkJoinPool 上。


ForkJoinPool 是为CPU密集型任务设计的,而这里的网络调用是阻塞I/O!在高负载下,这些阻塞调用可能会把线程全占满,嗯,后果非常可怕啊。


团队不是早有规定了吗,I/O任务必须用专门的虚拟线程执行器。


地雷2:失败处理


代码没有给出超时策略,如果 loyaltyClient 挂起了 30 秒,整个请求会被它拖住。


如果其中一个 future 失败会怎样?allOf 众所周知是要么全部成功要么失败的。


地雷3:违反了团队约定


小梁直接使用构造函数创建UserDashboardDTO,但是团队规范要求用静态工厂方法UserDashboard.from(profile, orders, status) ,这样可以统一处理 null、保证对象不可变。


小王更是犯了一个低级的错误:竟然假定调用API1和API2这两个操作互不影响!这两个API可是有依赖关系的啊。


张大胖把小梁和小王找来,质问他俩是怎么回事:“你们也是有几年经验的程序员了,为什么还会犯这种低级错误?”


两人支支吾吾了半天:“最近太忙了,我们用AI生成了一些代码。”


张大胖背后一凉:“坏了, AI在代码中埋的三颗雷,快要爆了! 赶紧回滚代码,fix bug!”


AI写得飞快,但只是会照着教科书写一个“看起来很对”的版本 ,但它不知道团队的一些约定,更是会忽略分布式系统至关重要的容错策略,例如超时、降级、部分响应等等。


这些小细节往往是埋雷的根源。



0 2
我们该怎么办?



AI其实就 像一个超级聪明,有无限耐心,但没经验的实习生。


它严重缺乏品味、智慧和架构远见。


你让它干啥它都干,但它不懂背景、不懂约定,也不懂“为什么要这么做”。


要想让AI真正帮上忙,需要你来带,就像你指导新人干活儿一样。




1.以人为主导的架构设计


别一上来就让AI写代码,先自己想清楚几个问题:


  • 这个功能要怎么融入现有系统?

  • 对下游应用有什么影响,失效的情况有哪些?

  • 最简单、最优雅的数据流是什么?

  • 需要创建新服务,还是扩展现有的?


做出了这些关键的决策以后,才开始向AI下命令来实现。


2. 给AI喂上下文


不要被动地使用AI,盲目地接受AI的输出,一定要给它提供上下文(规则、约定、示例代码)。


(1) 把团队约定告诉AI


例如 “我们对 DTO 使用静态工厂方法:UserDTO.from(user)。


错误用自定义 ApiServiceException 处理。


所有异步方法必须返回 CompletableFuture。


(2)提供示例


给它一段项目中已有的优质代码片段,作为风格范例。


(3)明确定义目标


例如 “我要在 Spring Boot 中实现一个 REST 控制器端点,它是非阻塞的、支持这三种查询参数,并调用 UserService。”


这样它写出来的东西才不会“跑偏”。


3. 深度调试


遇到Bug时,我们现在的第一反应就是把错误日志扔给AI,让它去修复。


这确实快,但治标不治本。


更好的做法是:把 AI 作为“提问伙伴”,用它来推动自己深入分析,而不是仅仅获得一个Bug Fix。


  • 不要只说“修复这个 NullPointerException”,而问:“在并发 Java 环境中,可能导致这个 NPE 的五种常见竞态条件是什么?”


  • - 不要只问“为什么慢?”,而问:“请分析这段代码的内存分配模式,有没有降低堆压力的机会?”


  • 不要只说“重写这个”,而问:“在这个特定任务中,使用 CompletableFuture 与 Java 21 的 Structured Concurrency 的权衡是什么?”


用 AI 提问来强化自己的技术思考(深挖根因、比较方案、权衡利弊),把工具变成学习催化剂而非快捷键。


4.工匠打磨:从正确到优雅


普通代码和卓越代码之间的最大差别是工艺感。

这最后的10%是AI做不到的,也是人类程序员介入的价值所在:把AI生成的代码变成别人阅读和维护都很愉悦的作品。


(1) 重命名:


把 data_list 改为 activeCustomerProfiles。AI 给出的是泛化名称,你赋予它们领域特定的意义。


(2) 添加“为什么这么做”的注释


删掉无用的注释(如 //对users进行循环遍历),改为解释为什么用这种方式实现(例如 // 这里用手工循环是因为对象较大,性能更优)。


(3) 简化并美化


拆分长函数,对齐参数以提升可读性,确保代码有着流畅的逻辑,能清晰地讲述一个“故事”。



0 3
写在最后


写代码这件事,从来就不是“谁敲键盘更快”的比赛。


AI确实能帮我们省下很多时间,但它不会替你理解复杂的业务、权衡架构的取舍,也不会替你承担线上事故的后果。


真正厉害的程序员,不是拒绝用AI,而是能驾驭AI。


用它写代码,而不是被它牵着走。


让AI负责“速度”,让人类保留“判断”和“品味”,那才是我们的价值所在。