AI 写代码之后,我开始只关心结果,不关心过程了

最近临时处理一批 Redis 数据,需要根据数据库查出的客户 ID,检查 Redis 中的 last_ipaddr 是否命中指定 IP。

几个月前 C家AI 已经帮我写过一个 Redis 查询脚本,原来的使用场景只是偶尔查一个客户。代码能正常运行,逻辑看起来也比较清晰,所以这次有批量查询需求时,我几乎没有多想,直接把原来的查询函数拿过来循环调用。

结果很反直觉:慢得离谱。

刚开始我怀疑的是一些很常见的问题。

是不是 print 太多?

是不是 Python 循环本身太慢?

是不是某些日志输出拖慢了执行?

于是我把大量打印和一些不必要的循环逻辑注释掉,再重新跑。

速度几乎没有改善。

这时才开始真正往查询函数内部看。

问题根本不在 Python 循环

继续检查 query_key() 后,我发现了真正的问题:

每查询一个客户,函数内部都会重新执行一次 RedisCluster(...)

也就是说,代码大致相当于:

for customer_id in customer_ids:
   redis_client = RedisCluster(...)
   query_key(redis_client, customer_id)

而不是:

redis_client = RedisCluster(...)

for customer_id in customer_ids:
   query_key(redis_client, customer_id)

看起来只差了一个位置,性能上的意义却完全不同。

每次重新初始化 RedisCluster 客户端,都可能涉及连接池、集群拓扑、槽位信息等初始化工作。

对于偶尔查询一个客户,这些固定成本几乎感觉不到。

但如果要查询几万甚至十万个客户,就相当于把原本只应该发生一次的初始化动作,重复执行了几万、十万次。

相比真正读取 Redis 数据的时间,这些初始化成本反而成了主要开销。

所以即使把 print 全部关闭,速度也不会有本质改善。

真正慢的不是“输出太多”,而是整个 Redis 客户端的生命周期设计错了。

单次查询–做了太多事情

继续看原来的函数,还发现了第二层问题。

之前为了把它写成一个比较完整、通用的 Redis 查询工具,单次查询大致会依次执行:

EXISTS
→ TYPE
→ HGETALL

先判断 Key 是否存在,再判断类型,最后把整个 Hash 全部读取出来。

在手工排查某一个客户的时候,这种代码其实很好用。

因为你不知道 Key 是不是存在,也不知道里面具体有什么内容,完整读取出来更方便观察。

但这次业务需求其实非常明确:

我只需要知道 last_ipaddr 是什么。

那就完全没有必要把整个 Hash 取回来。

直接:

HGET key last_ipaddr

就够了。

这也是一个很典型的问题:

单次查询时,“多做一点”几乎没有成本;到了批量场景,每一个多余操作都会乘以数据量。

一次多两三个 Redis 请求,看起来无所谓。

十万条数据时,就是几十万次额外操作。

最后的修改

确定问题后,修改方案反而很简单。

第一,把 Redis Cluster 客户端调整成按集群只初始化一次,后续所有查询持续复用同一个客户端及其连接池。

第二,针对当前业务,不再调用原来完整的 query_key(),而是直接读取:

HGET key last_ipaddr

第三,对于更大规模的批量查询,再使用 Pipeline 分批执行,进一步减少大量独立网络往返。

调整以后,性能提升非常明显。

最终处理 10 万个客户 ID 的 Redis 查询不到 1 分钟

问题解决以后,再回头看,其实这些优化都不是什么高深技术。

Redis 客户端要复用、只取需要的字段、大批量操作考虑 Pipeline,这些都是很常规的工程实践。

真正让我觉得值得记录的,反而是另外一个问题。

AI 写代码以后,我越来越容易把代码当成黑盒

以前自己写代码时,一个函数为什么这么设计,客户端在哪里初始化,循环里做了哪些事情,通常都是自己一点点写出来的。

所以即使代码写得不漂亮,至少对它的运行过程是有感觉的。

现在不一样了。

很多代码是直接描述需求,让 AI 生成。

代码出来以后,我首先验证的是:

输入对不对?
输出对不对?
功能能不能跑?

如果三者都没有问题,很容易就继续往下用了。

因为 AI 的价值本来就是减少执行层面的工作。

如果每一段 AI 生成的代码,我都重新逐行理解、重新推导一遍,那么效率优势也会被抵消很多。

而且随着 AI 写出来的代码越来越多,不同时间生成的代码结构、变量命名和实现风格也不完全一致。

理解和维护整个代码库的成本,会随着规模增加得非常快。

所以一种很自然的使用习惯开始形成:

我只要结果,过程只要暂时没有出问题,就先不管。

这其实很合理。

但这次 Redis 的问题让我意识到,这种习惯有一个风险:

AI 很容易给出“局部正确”的代码,而局部正确不等于放大以后仍然正确。

原来的代码其实没有错

现在回头看,我甚至不能简单地说 C家AI 当时生成的代码是“错的”。

原来的需求就是查一个 Redis Key。

在单次查询场景里:

  • 每次初始化一个 Redis 客户端,能运行;
  • EXISTS
  • TYPE
  • 最后 HGETALL

这些设计虽然不够高效,但完全可以接受。

如果一天只是手工查几个客户,根本不会有什么实际影响。

问题出现在场景发生了变化。

从:

帮我查一个客户的 Redis 数据。

变成了:

我要查10万个客户。

这两句话从业务逻辑上看,好像只是把“一次”变成了“很多次”。

但从工程实现上,它们已经不是同一个需求了。

连接生命周期、网络往返次数、内存占用、批处理方式,全部开始变得重要。

所以这次真正的问题,与其说是:

AI 没把代码写好。

不如说是:

我只告诉了 AI“要做什么”,没有告诉它“这个代码会怎么被使用”。

而数据规模,本身就是需求的一部分。

“让 AI 把代码写得更通用”可能也不是正确答案

一开始我的反思是:

以后让 AI 写代码的时候,应该尽可能要求它考虑通用性。

但再想一下,这个结论其实也不太对。

因为“通用”本身是有代价的。

如果我只是临时查一个 Redis Key,却要求 AI 一开始就设计:

  • 客户端生命周期管理;
  • Pipeline;
  • 批量接口;
  • 重试;
  • 超时;
  • 集群异常处理;
  • 日志;
  • 并发;
  • 性能统计;

很可能又走向另一个极端:过度设计。

一个只用两次的小脚本,最后变成一个小型 Redis SDK。

AI 尤其容易做这种事情。

你让它“考虑全面一点”,它真的可以考虑得非常全面。

结果可能不是更好维护,而是代码越来越重。

所以我现在更倾向于另外一种做法:

不是要求 AI 永远写得通用,而是在提需求时,把使用规模和边界告诉它。

例如,不只是说:

写一个函数,根据 customer_id 查询 Redis 中的 last_ipaddr。

而是说:

这个函数后续需要对数据库查出的约 10 万个 customer_id 执行查询,Redis 是 Cluster。请避免在循环中重复初始化客户端,并考虑减少网络往返;当前只需要读取 last_ipaddr 字段。

这时候 AI 得到的已经不是单纯的“功能需求”,而是一个带运行环境和规模约束的工程需求。

最后生成出来的代码,大概率从一开始就会完全不同。

AI Coding 之后,可能需要换一种代码审查方式

这次经历以后,我并没有得出一个结论:

以后 AI 写的代码,我一定要逐行审核。

我觉得这也不现实。

如果最终还是所有东西都自己逐行检查,AI Coding 的意义会下降很多。

但我会开始重点关注一些放大以后特别容易出问题的地方

比如看到:

RedisCluster(...)

我会问一句:

这个对象是在循环外创建,还是循环内创建?

看到数据库连接:

每条数据重新连接数据库了吗?

看到 HTTP 请求:

Session 有没有复用?

看到文件操作:

是不是每处理一条数据都重新打开一次大文件?

看到模型调用:

一百条数据是不是就调用一百次 API?能不能批量?

看到完整对象读取:

我真的需要整个对象,还是只需要其中一个字段?

我不会重新研究每一行代码。

但对于连接、网络、数据库、文件 IO、线程池、模型 API 这类有明显资源成本和生命周期的对象,会额外多看一眼。

还有一个问题也值得固定问一下:

如果数据量从 1 条变成 10 万条,这段代码还成立吗?

这个问题,可能比“代码有没有 Bug”更适合 AI Coding 时代。

最后

AI 的确让我越来越少关注代码是怎么一行一行写出来的。

以前自己写代码时,很多过程其实不需要刻意理解。变量为什么放这里、连接在哪里建立、一个函数会调用几次,因为代码就是自己一点点写出来的,这些东西天然存在于脑子里。

现在这个过程被 AI 接管了。

我描述需求,AI 给出代码,我运行,检查结果。如果输入正确、输出正确,很多时候这段代码就会继续被使用。

从效率上来说,这其实没什么问题。

如果用了 AI 以后,我还要求自己把所有生成代码重新逐行推导一遍,那某种程度上又把 AI 带来的效率还回去了。

所以我并不觉得“只关心结果”本身是一件坏事。

真正的变化可能是:过去我们通过亲自写代码,自然而然地理解过程;现在过程被 AI 隐藏以后,这种理解不再自动发生了。

这次 Redis 查询只是一个很小的例子。

代码一直能运行,结果也一直是对的。直到使用场景从查一个客户变成查十万个客户,之前被隐藏起来的实现方式才突然变成问题。

它让我意识到,AI Coding 之后,“能跑”和“我理解它为什么这样跑”正在逐渐变成两件不同的事情。

而工程师可能也需要接受这种变化。

我们未必还需要像以前一样掌握每一行实现,但至少要知道:哪些地方自己已经交给了 AI,哪些假设自己其实从来没有验证过。

AI 写得越多,代码本身可能越像黑盒。

人的工作也会越来越从“亲手完成过程”,转向“定义问题、判断结果,以及决定什么时候必须打开这个黑盒”。

我现在依然更愿意让 AI 去写代码。

只是下一次当一段代码从“能用”变成“要真正拿来用”的时候,我大概会更有意识地提醒自己:

结果正确,不代表过程已经不重要。只是以前过程是自己写出来的,现在需要自己决定,什么时候值得重新看进去。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇