跳至主要内容
AI 省下的是打字,没省下的是判断

AI 省下的是打字,没省下的是判断

上周三下午,我把一个内部服务的鉴权从 session 换成 JWT。整个过程四十分钟,其中我真正敲键盘的时间大概不超过五分钟。剩下的三十五分钟,我在翻旧代码确认哪些路由挂了鉴权中间件、在补一个过期 token 的测试、以及在日志里找一个「为什么登出之后会话还在生效」的原因。

这个比例差不多就是我感受到的 AI 辅助编程:它把「写」这一段压得很短,然后把省下来的时间原封不动地还给了「想」和「验」。

变快的部分,比我一开始以为的窄

我最初以为提效会均匀分布在所有编码环节,用了半年才发现不是。真正明显变快的活有下面几类:

  • 样板代码。CRUD 的 handler、配置结构体的解析、错误包装、参数校验。写起来没有智力挑战,但一行一行敲很费时间。
  • 跨语言翻译。把一段 Python 的 CSV 清洗逻辑翻成 Go。我两种语言都熟,只是不想写,这类任务模型的质量很高。不过要注意,我上次翻三百多行,编译一次就过,但我核对浮点数格式化和空行处理花的时间比手写还长。所以现在我只在「输出可以立刻用眼睛比对」的翻译任务上用它。
  • 查 SDK 用法。比翻文档快,但版本经常是旧的,签名对不上,得回头核对一眼。
  • 机械重构。改函数签名、批量替换调用点、把一堆 if err != nil 换成包装函数。这类改动有编译器兜底,错了立刻知道。
  • 测试骨架。表驱动测试的框架、mock 的样板,它写得比我快。

这些活的共同点是:验证成本低于手写成本。正确性要么由编译器保证,要么能一眼看出来。这条线大致就是分界线,越过它的任务,提效就开始变成负的。

比如重试逻辑。我让模型写一个带退避的 HTTP 调用,它给出来的东西能跑,但指数退避没有 jitter,也没有上限:

GO22 行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func doWithRetry(ctx context.Context, req *http.Request, max int) (*http.Response, error) {
var lastErr error
for i := 0; i < max; i++ {
resp, err := http.DefaultClient.Do(req.Clone(ctx))
if err == nil && resp.StatusCode < 500 {
return resp, nil
}
lastErr = err
// 下面两行是我补的:加抖动、加上限,并且能被 ctx 打断
base := time.Duration(1<<i) * 200 * time.Millisecond
if base > 5*time.Second {
base = 5 * time.Second
}
sleep := base/2 + time.Duration(rand.Int63n(int64(base/2)))
select {
case <-time.After(sleep):
case <-ctx.Done():
return nil, ctx.Err()
}
}
return nil, lastErr
}

没有 jitter 的重试,在服务端一起抖的时候会变成整齐的重试风暴。这个问题模型不会主动提,因为它写的是「一个能用的重试」,不是「我们这套系统里能用的重试」。

没变的部分,反而更贵了

JWT 那个例子更典型。模型给的版本十分钟就能跑通登录和校验,但有三件事它不会管:

一是密钥轮换。签发和校验用的是同一个硬编码字符串,换密钥那天所有在线用户的 token 一起失效。二是时钟偏移。校验里没有留容差,两台机器差几秒就开始随机 401。三是权限放进了 token 的 claim 里,改一次权限要等 token 过期。

这三件事都不是代码问题,是这套系统怎么运行的问题。模型看到的是我贴进去的那几百行,看不到发布流程、看不到运维半夜会不会被叫起来、看不到三个月后接手的人是谁。

所以没变的东西,我列一下:

需求澄清。模型不会问我「这个字段为空时导出成空串还是跳过这一行」。它只会挑一个看起来合理的,然后继续往下写。这个问题以前也存在,只是以前我自己写的时候会停下来想两秒,现在这两秒被省掉了,代价是错的东西被写得更快更多。

边界条件。空集合、时区、幂等、并发下的重复提交。这些是需求的暗面,需求文档里通常不写,模型的训练数据里也不会有。

可观测性。我几乎没见过模型主动加日志和指标,除非我明确要求。它不觉得「这个分支走进去意味着出事了」值得记一笔。

因果推理。线上出问题时,还是要人从日志和时间线里拼出「到底发生了什么」。这一段的效率我没觉得有任何变化。

我的工作流被挤到了两头

现在我的编码过程更像三明治,两端是我,中间是模型。

前端是把话说清楚:类型定义、接口签名、失败的用例、不能碰的约束。这部分时间明显变长了。我经常花十分钟写一段注释和类型,只为了让接下来生成的两百行有个边界。

后端是验收。我现在看一段生成的代码,第一个问题不是「它写得对不对」,而是「它在什么情况下会出错,我怎么才能知道」。语法层面的错误基本消失了,意图层面的错误变多了,评审的重心整体上移了一层。这对我其实是个好消息,因为读代码判断意图本来就是我更擅长、也更愿意干的事。

被压缩掉的是中间那段。我写测试的顺序也变了:先写一个会失败的测试,再让模型填实现。测试必须我自己写,因为它表达的是「我要什么」,这个东西一旦外包出去,后面就没有验收基准了。

有一点我还没定论:这样跳过手写样板的阶段,一个刚入行的人还能不能建立起底层的心智模型。我自己的情况是,我先手写过很多遍分页、很多遍重试、很多遍错误包装,才练出了一眼看出生成代码哪里不对的能力。这个顺序能不能反过来,我没有答案。我倾向于是不能,但也不想拿这个去劝退别人。

一个我现在的习惯

每次模型给出一段能跑的代码,我会先问自己一句:这段代码如果出错,第一个发现它的是谁?

如果答案是「编译器」「测试」或者「我自己在本地跑一遍」,那就放心用。如果答案是「三天后的用户」或者「半夜的告警」,那我就得自己从头再想一遍,哪怕最后写出来的东西和它给的差不多。

这个问题模型答不了,因为它不知道我的系统里,谁在承担出错的代价。

关于本文

由 Lu's Blog 撰写,采用 CC BY-NC 4.0 许可协议。

#AI 编程 #代码评审 #工作流

评论