现在的 AI 修 BUG 确实名声在外,号称能替代程序员。刚好手里攒了个 Codex 的坑位,我便拿自家的 定时任务系统(haskell-periodic) 开了刀。
这个项目非常底层,对并发和数据流的要求极严。本以为是请了个"神医",结果发现这哥们儿是个"顶级庸医",差点没把我代码库给送走。
准备工作:配置 Codex
安装和配置倒是不难,几步搞定:
-
安装:
npm install @openai/codex -
配置 Azure GPT-5.3-Codex: 先随便填个无效 Key 把程序跑起来,然后去
$HOME/.codex修改配置文件:Ini, TOML
inimodel = "gpt-5.3-codex" model_provider = "azure" model_reasoning_effort = "medium" approvals_reviewer = "user" [model_providers.azure] name = "Azure OpenAI" base_url = "https://your-server.cognitiveservices.azure.com/openai" query_params = { api-version = "2025-04-01-preview" } env_key = "AZURE_OPENAI_API_KEY" wire_api = "responses"记得把真正的 Azure Key 塞进环境变量里。
改完后先打个招呼测试一下,等它回个"你好"确认成功后,真正的"好戏"就开始了。
现场实测:AI 的"无限修复"表演
我移步到项目目录,给 Codex 下了个指令:"analyze haskell code, find bug and fix it"。
Codex 表现得非常专业,扫描代码、定位 BUG、给出修改建议。每改完一个,它都会乖乖等我确认。我当时心想:这效率,以后我是不是可以只负责点 continue 了?
结果,我接受了几个建议后,越想越不对劲。这哪是修 BUG,这简直是在制造"赛博地雷"!
1. 制造隐性错误的"防崩"修法
原代码在读取数字类型的命令行参数时,直接用了 read s。如果参数输错了,程序会报错崩溃。Codex 觉得这不优雅,给我改成了:
Haskell
ini
-- AI 修正后的 safeRead
safeRead :: Read a => a -> String -> a
safeRead def s = fromMaybe def $ readMaybe s
细思极恐:
这看起来很安全,但其实造了一个超高级 BUG。
在定时任务这种系统里,参数输错了必须报错 ,让我立马知道配置有问题。现在好了,它默默给个默认值继续跑,我可能半天都发现不了系统正在按错误的逻辑运行。这种静默失败比程序崩溃要致命得多。
2. 把正常的逻辑改"丢"了
在处理命令行参数解析时,原来的代码是:
Haskell
rust
-- 原代码
getFlag :: [String] -> [String] -> String
getFlag _ [] = ""
getFlag _ [_] = ""
getFlag ks (x:y:xs) = if x `elem` ks then y else getFlag ks (y:xs)
Codex 大手一挥,把它改成了:
Haskell
rust
-- AI 修改后
getFlag :: [String] -> [String] -> String
getFlag _ [] = ""
getFlag _ [_] = ""
getFlag ks (x:y:xs) = if x `elem` ks then y else getFlag ks xs
感受到 BUG 了吗?
原代码在匹配不到 flag 时,会递归检查 (y:xs),因为 y 可能是下一个 flag。但 AI 改完后直接跳过了 y。这意味着如果参数里连续出现两个 flag 或者格式稍微复杂点,部分参数就会直接凭空消失。乍看逻辑通顺,实则业务全乱。
3. 亲手撸出一个"死循环"
这是最精彩的部分,关于网络包接收的代码:
Haskell
ruby
-- 修改前的关键逻辑
case decodeOrFail (BL.fromStrict hbs) of
Left (_, _, e0) -> do
putBack $ hbs <> crcbs -- 这里只放回了 header 和 crc
throwIO $ PacketDecodeError $ "Packet header: " <> e0
AI 觉得这里的 putBack 不够完整,帮我"优化"了一下:
Haskell
ruby
-- AI 修改后
case decodeOrFail (BL.fromStrict hbs) of
Left (_, _, e0) -> do
putBack $ magicbs <> hbs <> crcbs -- AI 贴心地把 magicbs 也塞回去了
throwIO $ PacketDecodeError $ "Packet header: " <> e0
真是一口老血喷出来:
在协议解析里,magicbs 是已经探测完的包头标识。AI 把 magicbs 重新塞回缓存区,下一次循环它又会把这一段当成新包去解析,然后解析失败,再塞回去......一个逻辑完美的死循环诞生了。
4. 送你一个"空间泄漏"大礼包
Haskell 程序员最怕的就是 Space Leak,AI 显然想帮我体验一下这种痛苦:
Haskell
vbnet
-- 原代码:用 forever 运行
runCheckNodeState ... = void . async . forever $ do ...
-- AI 修改后:改成了递归 loop
runCheckNodeState serveStateTVar ... = void $ async loop
where loop = do
...
serving <- readTVarIO serveStateTVar
when serving $ loop
一眼看穿:
这种手写的递归 loop 如果没有处理好惰性求值,在长时间运行后,堆栈里会堆积大量的 Thunk。系统跑得越久,占用的内存就越多,直到内存耗尽。这种内存泄漏在本地测试很难发现,一旦上线就是定时炸弹。
思考总结:AI 写代码,心要大,眼要尖
经过这一番折腾,我悟出了一个道理:
AI 修 BUG 确实很牛,但它目前还不具备"业务常识"和"全局视野"。 它看到的只是局部代码的美观和健壮,却不理解底层网络流的不可逆性,也不理解 Haskell 严苛的求值模型。
用 AI 写代码,真的得长点心。如果你对自己的项目没有绝对的掌控力,AI 丢给你的可能不是"解药",而是包装得非常漂亮、甚至连你也看不懂的高级 BUG。
结论: 别让 AI 掌握"最终解释权",Review 代码的时候,要把自己当成它最严厉的教官!
老哥们,你们用 AI 修代码还遇到过什么奇葩坑?评论区聊聊。