一个差点被我提交的补丁

前几天我在修一个小毛病。我平时用 cc-connect 把 Claude Code 桥接到微信,在手机上跟它对话。问题是:超过 3800 字符的回复会被切成两条,而切分是按字符数硬切的,不管 Markdown 结构。于是切点落在代码块中间时,第一条以未闭合的围栏结尾,第二条以裸文本开头——等宽字体没了,长行折行,行号和内容错位成一团。

助手给的第一个方案是往系统提示词里加一句话:

外发文本在 3800 字符处硬切且不认 Markdown 结构,因此绝不能让单个表格或代码块跨越这个边界。

这方案有效。加上之后模型会主动控制输出长度,症状确实没了。理由充分,注释清楚。我要是当时点头,这事就算”解决”了。

但它是一块创可贴。

真正的毛病在代码里:cc-connect 的微信适配器 platform/weixin 用了自己写的 splitUTF8,纯按字符数切。而同一个仓库的 core 包里,早就躺着一个 SplitMessageCodeFenceAware——它按行切分,在分块结尾补上闭合围栏、下一块开头重新打开,保证每条消息自己都是合法的 Markdown。Telegram、Discord、qqbot 三个适配器加上引擎自己,都在用它。只有微信这一个还留着自研的切分函数。

正确答案在同一个仓库里躺着,被四个地方调用着。而 AI 的第一反应是绕开它,用提示词把成本转嫁给之后的每一次对话。

什么叫”创可贴”

先把话说准,不然容易变成情绪化吐槽。创可贴不是指代码写得丑,也不是指没用设计模式。它有一个很清楚的标准:

修改发生在症状出现的地方,而不是毛病所在的地方。

按这个标准,上面那个提示词方案是标准创可贴:毛病在切分函数里,修改落在提示词里。症状不再出现,毛病原封不动,而且从此以后,每次对话都要为它付一点注意力的钱。

创可贴的麻烦在于,它在当下几乎总是更快、更安全、更好解释的。不动既有代码,不会引入回归,评审也挑不出毛病。它的代价全记在未来的账上——而未来没人签字。

AI 为什么爱打补丁

把这归结为”AI 笨”是偷懒的解释。真实原因有三条,而且都不是能力问题。

第一,它看不到全貌。 人在一个项目里待半年,脑子里会长出一张地图:哪些轮子已经有了、哪个模块是历史包袱。这张地图不在任何文件里。而模型每次进来,只看到你问的那几个文件。上面那个例子,如果没人主动去搜,SplitMessageCodeFenceAware 就永远进不了它的视野——它不是不想用现成的,是根本不知道有。

这带来一个反直觉的结论:AI 越”高效地只看必要文件”,就越容易重复造轮子。

第二,考核指标错了。 不管是训练还是验收,判断标准几乎都是”问题解决了吗”,而不是”结构变好了吗”。大多数时候这俩指向同一个方向,但恰恰在需要重构的关键时刻会分岔。一个 5 行的绕过方案,和一个要动三个文件、删一个函数、改两处测试的方案摆在一起,前者在”解决问题”上得分更高、风险更低、汇报成本更小。

模型会选它。说实话,赶着下班的人也会选它。

第三,它没有被自己坑过。 创可贴的代价是延迟兑现的:三个月后有人踩坑,半年后没人敢动这段代码。人之所以慢慢养出”这里不能凑合”的直觉,是因为亲自付过这些账——维护自己当年偷懒写下的代码,那种窒息感才是品味的来源。

而模型没有未来。每次对话都是崭新的,没人会回来告诉它”你上次那个补丁害惨我们了”。

但故事还有另一面

如果就写到这,那不过是又一篇”AI 只会缝缝补补”的老生常谈。而这不符合我看到的事实。

还是同一天、同一个助手,另外几件事上它的判断相当漂亮:

没有造轮子。 要解析一个几百行的 Excel 表格时,它没手写 xlsx 解析(这可是个看起来”就几十行”的诱人活儿),而是判断现有依赖不覆盖、直接装了现成的库。

没有加重试,而是挖到了根。 一个跑了很久却天天空转的脚本,表面症状是”搜索没结果”。最省事的修法是加重试、换搜索词。它没有,一路查到了真原因:请求体被转义后塞进远程 shell 的单引号里,API 收到非法 JSON 返回了 422,而 curl 的退出码是 0,于是脚本把错误当成了”没有结果”。它顺手点出了更隐蔽的那个设计缺陷——只看退出码、不看响应内容,是一种会静默吞掉错误的写法。

做了一次止损判断。 我问”要不要改源码”,它先去查了正在运行的二进制版本,发现和本地源码根本不是同一个 commit,于是结论是:改源码不生效,就算重新编译也会被下次升级覆盖,所以正确的位置是配置层。这不是编码判断,是”改动该落在哪一层”的设计判断。

所以”AI 写的代码没有设计性”,作为一句全都算数的断言,是站不住的。同一个模型、同一天、同一个人在用,有时给创可贴,有时给干净的设计。

区别到底在哪?

分水岭不在能力,在提问

回到开头。当时的方案是”改一句提示词的文案”。让它转向的,是我随口问的一句:

这个如果默认就支持 md,像飞书,还需要这种的特殊说明吗?

这句话没提供任何新信息。我没告诉它有那个现成函数,也没指出切分有问题。我只是把问题从”这句话怎么改”抬到了”这句话该不该存在”。

然后它去查了 cc-connect 的全部 20 个平台适配器:只有 6 个实现了 FormattingInstructions 这个可选接口,而飞书、Telegram、Discord、钉钉这些渲染标准 Markdown 的平台一概不实现——因为这接口本来就是给语法特殊的平台开的例外,它的文档注释里举的例子正是 Slack 的 mrkdwn。于是方案从”改文案”变成了”删掉这个方法”。顺着这条线继续查,才翻出那个真正的 bug:切分函数用错了。

最后提交的两个 commit,第二个比第一个有价值得多,而它完全来自那一句质疑。

结论很清楚:AI 有能力做设计判断,但这个能力不会自动启动。它需要一个触发器,而触发器几乎总是一句把问题层级抬高的话。

问”这个报错怎么修”,得到补丁。
问”为什么会有这个报错”,得到修复。
问”这东西为什么存在”,才可能得到设计。

五条创可贴自检清单

道理不好用,给一份能直接照着核对的。AI 给出方案时,命中任何一条就值得再追问一句:

1. 改的地方和出问题的地方对不上。 毛病在切分函数里,补丁打在提示词里。这是最强的信号。

2. 用配置、提示词、参数去绕开代码行为。 这类方案都带着”不动代码所以很安全”的光环,而这恰恰意味着毛病被永久留下了。

3. 新写的东西,项目里其实已经有了。 每次都值得问一句:这功能框架是不是自带?仓库里是不是已经有人写过?

4. 说不清”原来为什么是错的”。 真修复总能配一句干脆的因果话。如果解释要绕好几个弯、全是”考虑到……的情况”,那多半是在适配症状。

5. 没有能对应上的测试。 如果一个修复写不出”改之前失败、改之后通过”的测试,要么没修到点上,要么修的是个不存在的问题。

(还可以加半条:出现”先这样,以后再改”的措辞——这个”以后”的兑现率极低,等真要改的时候,调用方已经长出来了。)

人的位置变了

我不想得出”所以还得人来写代码”的结论,那是对现实的误判。论实现速度和覆盖面,模型已经超过大多数人,包括我。

真正的变化是稀缺的东西换了。当实现成本趋近于零,写出来不再是瓶颈,判断”该不该这么写”成了唯一的瓶颈。过去一个工程师的价值里,也许七成是”能写出来”、三成是”知道该怎么写”;现在前者迅速贬值,后者变成了全部。

这对人的要求其实更高了:你得在看不懂每一行代码的前提下,仍然守得住设计的边界。

我现在固定用三句话压方案:

这个能力项目里是不是已经有了? —— 防重复造轮子。开头那个例子,这一问就能问出那个现成函数。

你改的地方,是问题真正发生的地方吗? —— 防打偏。

如果不绕过去,正确做法是什么?成本多少? —— 关键在于它不否定绕过方案,而是逼两个方案同台比较。很多时候你会发现正确做法便宜得多。

最后

AI 是个放大器。你有设计能力,它把你的设计能力放大十倍;你不求甚解,它把你的不求甚解也放大十倍,而且债生成的速度快到你来不及察觉。

它不会替你决定要盖一座什么样的房子。它只会以惊人的效率,把你指的那面墙砌起来——哪怕那面墙根本不该在那儿。

开头那个案例最后提成了一个上游 PR(chenhg5/cc-connect#1766):两个 commit,两个文件,净改动 +43 −39 行。第一个 commit 删掉一句过时的提示,第二个换掉那个用错的切分函数。全过程可以点进去核对。

第二个 commit 不在最初的计划里。它来自一句”还需要这种特殊说明吗”。

参考资料