当代码不再稀缺 - 01

Share
当代码不再稀缺 - 01
Photo by Ilya Pavlov / Unsplash

AI 几乎写完了代码,项目为什么只从六个月缩短到四个月?

我们最近做了一个床位管理项目。

按照过去的开发方式,团队估计大概需要半年。这一次,AI 几乎取代了人工代码输出,最后项目用了四个月。

听起来不错。但有一个问题一直让我不太舒服:既然写代码这件事几乎都被 AI 接管了,项目为什么只是从六个月变成四个月,而不是两个月?

后来我们发现,省下来的时间并没有完全消失。它只是从 coding 转移到了 code review 和 QA。

Coding speed 不等于 delivery speed

过去做项目,engineer 的日常大概是:想一想,写一写,跑一下,再改一改。

现在变成了:想清楚,让 AI 去做,然后验证它做出来的东西。

前半段确实快了很多。一个实现方案可以很快生成,修改也便宜,原型更是一天能迭代好几轮。但与此同时,我们发现 code review 和 QA 的绝对耗时都增加了。

Reviewer 不再只是看代码能不能跑,还要补足更多上下文:

  • 这个局部实现为什么成立?
  • 它依赖了哪些没有写出来的假设?
  • 它与现有架构和抽象是否一致?
  • 局部最优放进整个系统后,会不会变成全局 corner case?

这里还有一个我们正在形成的假设:因为代码是 AI 生成的,reviewer 默认更不信任它。

以前 review 熟悉工程师的代码,我们可能更多按 method、module、contract 和架构边界去判断;面对 AI 代码,第一反应却是退回去逐行检查。毕竟谁也不敢对一个不承担 on-call 的作者给予太多信任。

这带来了一个 review paradox:我们看得更细,却不一定看得更对。

床位占用的 bug 就是如此。逐行看,绝对时间是否落在预约区间的逻辑没有明显问题;真正缺失的是更高一层的 business-day invariant。我们认真检查了 implementation,却没有重新确认它究竟在回答哪个业务问题。

另一个现象是 code inflation。AI 可以很快完成眼前的局部任务,但它不一定知道系统里已经存在相似实现,也不一定主动停下来做 abstraction 和 refactoring。于是本来应该复用的逻辑被重新写一遍,本来应该抽象的地方继续叠代码,代码量和 PR size 都开始增长。

更多局部可用代码
→ 更多重复和未完成的抽象
→ 更大的 review surface
→ 更高的认知负担
→ 更长的 review 和未来维护时间

不过,目前应该把这两点叫作假设,而不是通用结论。Review 变慢也可能来自 PR 过大、业务本身复杂或上下文不完整;冗余代码也可能来自团队没有给 AI 足够的架构约束和 refactoring gate。要验证它们,后续需要比较 AI 与人工代码的平均 PR size、review time、duplicate-code ratio 和 LOC per feature。

QA 面对的也是类似问题。AI 很容易把 happy path 做得又快又顺,但床位管理不是一道 LeetCode。权限、时间边界、状态转换和多个条件组合起来,permutations 很快就会失控。

一行逻辑没错,整个业务错了

我们就遇到过一个很典型的例子。

床位系统只以“天”为单位追踪占用。为了实现这个模型,团队约定所有床位在当天 UTC 12:00:00 PM check-in,在次日 UTC 11:59:59 AM check-out。

这不是普通的 implementation detail,而是整个床位占用模型成立的 business invariant。

但 engineer 没有把这个假设完整交给 AI。AI 根据自己拿到的上下文,生成了一个看起来完全合理的实现:用查询发生的绝对时间,判断当前时刻是否落在预约区间内。

问题来了。

如果客户在 UTC 中午 12 点之前查询一个当天已经被预约的床位,预约在系统看来还“没有开始”。于是 API 会告诉客户:这个床位没有被预约。

从局部代码看,时间区间判断没有明显问题;从真实业务看,我们问的是“这一天是否已经被占用”,而代码回答的是“当前这一秒是否落在预约区间”。

AI 很认真地回答了错误的问题。更尴尬的是,我们也很认真地 review 了这个错误答案。

这个 bug 没有在 code review 中暴露,也没有被 QA 覆盖,最后是客户反馈后才被发现。

这不是一个“AI 写了烂代码”的故事

严格来说,这个 bug 不能简单甩锅给 AI。人类工程师也可能遗漏同样的时间边界。

真正的问题是:关键的 business invariant 只存在于人的脑子里,没有成为 AI 输入的一部分,也没有成为测试和 review 的对象。

当代码主要由人写时,engineer 可能在实现过程中慢慢补齐这些隐含上下文。这个过程很慢,有时甚至有点磨人,但思考和实现往往交织在一起。

AI 把实现压缩成几分钟之后,这段“边写边发现问题”的时间也一起被压缩了。如果 intent 不完整,AI 不会停下来替团队承担业务责任。它只会更快地产生一个 plausible implementation。

所以 AI 加速的不只是正确代码,也可能是未经验证的假设。

EM 最容易做错的事:要求更多 output

看到 coding 变快,一个很自然的管理反应是:既然有了 AI,每个 engineer 就应该完成更多 ticket、提交更多 PR。

但如果 generation capacity 提高了,verification capacity 没有同步提高,系统很快会变成:

更多代码和 PR
→ reviewer queue 变长
→ QA queue 变长
→ WIP 增加
→ feedback 变慢
→ 更多上下文切换和返工

管理层看到的是 engineer output 增加,交付系统感受到的却是 downstream congestion。

我们这个项目的最终缺陷和返工反而减少,并不能简单解释为“AI 写得比人好”。更可信的解释是:AI 加速了 implementation,同时团队在 review 和 QA 上投入了更多时间。

如果维持过去的 review 和 QA 投入,只享受 AI 带来的代码生成速度,我的判断是,缺陷一定会增加。

换句话说,AI productivity 不是免费收益。团队需要用新的 verification system,才能把 generation speed 转换成真实的 delivery speed。

EM 应该开始问另一个问题

过去做项目,我们经常问:

这个功能要写多久?需要几个 engineer?

在 AI 时代,还需要再加一个更重要的问题:

团队需要多久,才能获得足够的 evidence,相信这个功能是正确的?

这意味着 EM 不能只看 ticket、PR 或代码产出,还要开始看:

  • PR 等待 review 的时间;
  • reviewer 和 QA queue;
  • 一个变更涉及多少业务状态和 system invariants;
  • 从 implementation ready 到获得可信验证的时间;
  • escaped defects;
  • 从 intent 到 production evidence 的端到端 lead time。

AI 几乎写完代码,并不意味着项目会按同样比例缩短。它加速的是 generation,而软件交付仍然受 verification capacity 限制。

当代码不再稀缺,EM 真正要管理的,不是谁能写多少代码,而是团队能多快把 intent 变成 verified outcome。

下周可以做的一个小实验

找一个最近完成的项目,把时间粗略分成三段:

  1. implementation;
  2. 等待和执行 review;
  3. 等待和执行 QA / verification。

然后不要先问谁的效率不够高,先找最长的 queue 在哪里。

AI 时代,第一个需要升级的可能不是 coding tool,而是我们看待软件交付的方式。

Read more

低绩效同事,先别急着下结论

低绩效同事,先别急着下结论

团队里最难处理的,往往不是最差的同事,而是那些卡在board line上,挤挤能出活、但不挤就磨洋工的low performer 同事。 这类 low performer 一出现,很多Manager的第一反应就是:ownership 不行。问题是,这句话太空了,空到几乎没法行动。 我更喜欢用一个简单的框架看这件事:先判断原因,再调整支持,接着设观察期,最后做去留决定。 先判断:到底是哪一类问题 low performance 不等于 low ability。常见情况其实不一样: * 不会做:能力还没到,拆问题、推进、沟通都卡住。 * 不想做:知道怎么做,但投入不够。 * 做错方向:很努力,但目标理解偏了。 * 岗位不匹配:人不差,只是不适合这个角色。 比如: * 一个同事总是晚交,但每次都能把问题讲清楚,可能是任务切太大了。 * 一个同事总返工,

By andy

Vibe coding 之踩坑记

Vibe coding 对于程序员的技能要求反而比之前高了 先讲一个故事。 我这几天突然被隔壁组叫去,帮他们解决一个支付系统的问题。 简单来说,我们的客户分成机构和下属的医疗场所。一个机构下面可能有多个场所,然后机构可以统一管理下属场所的账单,支付等等。这样就需要我们能在我们的支付系统里面建立一个关系树,让父结点能看到子节点的账单,并支付。 问题就出在这个关系树上。因为我们之前用过一个另外的支付系统,所以有一部分老客户是从之前的系统迁移过来的。 这个老系统给每个客户创建了一个独特的id,但是这个id 和我们内部使用的id 并不一致,所以我们需要自己把客户的id 和他们的支付账户连接起来。然而: * 新的支付系统只支持固定的一些属性进行搜索。所以之前做这个项目的工程师A耍了个小聪明,用客户的last-name 属性存储了我们的id * 第一个雷:用户的last-name是可以由用户自行修改的。如果他们修改了,我们的对应关系就乱套了。 * 但是这个id A存错了,应该存储机构的id,A存成了下属场所的

By andy

关于flashblocks

争取整个短文系列,迎合短视频的潮流。 base前段时间整了个活,提出了一个新的概念叫flashblocks。 在有flashblocks之前,每个区块的状态是要么这个区块还不存在,没法拿到区块的任何状态和信息;要么就是这个区块已经存在了,它的状态是不可变的,拿到就是最终区块。base链每2秒产生一个新的区块,即任何的交易需要2秒钟才能确认。 如下图,区块A生成之后,2秒内用户只能看到区块A,直到区块B生成。 flashblocks呢,它允许用户提前获取正在打包的区块的状态。正在打包的区块每200ms更新一次状态,直到10次更新之后,正在打包的区块变成一个完整的区块。 如下图,区块A打包200ms后,用户可以立刻看到区块B的状态0. 再200ms后,可以看到区块B的状态1,直到区块B的状态9,然后区块B稳定,开始生成区块C的状态1。 flashblocks的好处是什么呢?它可以在一个区块完全打包好之前,让用户提前知道自己的交易是否有被打包进当前的区块,加速交易状态的确认。 缺点又有哪些呢? 首先这个确认属于预确认(preconfirmation), 这个预确认还没有状态更新

By andy