代码很便宜,判断力很昂贵

为什么在 AI 时代,编程基础重新变得重要,以及为什么今天 code review 中最重要的问题是:“事情本来就应该这样做吗?”

到了 2026 年,程序员最有价值的能力已经不再是写代码,而是能够对 AI 说:这段代码是错的——并解释为什么。

这听起来像会议幻灯片上的口号,所以让我们从一个没那么吸引眼球的东西开始:一个 pull request。

Agent 接到任务:“为购物车中的商品添加预留功能”。几分钟后,PR 就出来了:几百行代码、一个新 endpoint、一项 migration、十几个测试,全部通过。变更说明写得比一个周五下午疲惫的人类可能写出的还要认真。Review 只花了五分钟,因为一切“看起来都很好”。

三周后,在促销期间,商店卖出了 40 件库存其实只有 12 件的商品。

代码能运行。测试能通过。方案却是错的。我们稍后还会回到这个 PR。

本文并不是说 AI 会写出糟糕的代码。它经常写得比普通水平更好。本文要表达的是另一件事:AI 从根本上降低了生成代码的成本,却提高了评估这些代码是否正确、安全、可维护,以及在架构上是否恰当的能力价值。 如今最有价值的程序员,不是最快产出代码的人,而是能够判断一个方案是否本就应该以这种形式出现的人。

简而言之:

  • 代码生成的成本下降速度快于代码验证的成本。瓶颈已经从编写代码转移到了 review 和判断上。
  • AI 生成的代码很少只是单纯出错。更常见的是局部正确:测试通过,但数据模型错误、存在竞态、授权有漏洞,或者会损害系统的其他部分。
  • 一次性写入测试、约束和权限中的判断力可以规模化。每个 PR 都重新应用一次的判断力则不能。
  • 在应用内部运行的 AI 应当拥有其所代表用户的权限,而数据变更应由人类批准。
  • 完全把工作委托给 AI 的初级开发者,学到的东西明显少于向 AI 询问解释的人。

代码正在成为商品。决策不会

Django 联合创始人、Datasette 作者 Simon Willison,在《Agentic Engineering Patterns》指南开篇便写道:“Writing code is cheap now”。其核心观点很简单:产出第一版可运行代码的成本几乎降到了零,但交付高质量代码依然代价高昂。

比这一观点本身更有意思的是 Willison 如何定义“好代码”。代码必须能运行,但还必须:能够被验证、解决正确的问题、合理处理错误、尽可能简单、具备测试、拥有最新文档、能够在未来被修改,并满足安全性、可靠性等质量要求。

在这份清单中,默认变得更便宜的只有第一项。其余全都是决策。

Extreme Programming 的创造者 Kent Beck 在一篇写于 2023 年 4 月、首次尝试 ChatGPT 后发布的文章中,以另一种方式描述了同一现象:Beck 90% 的技能价值降到了零,而剩余 10% 的杠杆效应提升了一千倍。在展开这一观点的文章中,这 10% 指的是知道什么值得做、该问什么问题,而不是熟练地逐字组织语言。

还有一个更简单、经济学上的解释。Joel Spolsky 在 2002 年的文章《Strategy Letter V》中重申了一条基本原则:当某种产品变得更便宜时,对互补产品的需求就会增加。代码和代码评审是互补的。当代码变得更便宜,产生的代码就更多,而每一行新代码都需要有人决定它是否应当进入生产环境。

最早的论证可以追溯到 1986 年。在《No Silver Bullet》中,Fred Brooks 将软件复杂性分为偶然复杂性和本质复杂性。偶然复杂性源于工具:语法、样板代码、在文档中查找签名。本质复杂性源于问题本身:系统应当做什么、哪些条件必须始终成立、当某些东西出错时会发生什么。Brooks 认为,任何单一工具都无法带来数量级的生产力飞跃,因为它无法消除本质复杂性。

LLM 是我们见过的最有希望成为银弹的候选者。但在绝大多数情况下,它们攻击的是偶然复杂性。本质复杂性仍然停留在原来的地方——人类这一侧。


更多代码并不意味着更多软件

如果代码生成是瓶颈,那么更便宜的代码应当直接转化为更快交付可运行的软件。数据呈现出更复杂的情况。

来源研究内容结果注意事项
Faros AI,2025来自 1255 个团队、超过 10,000 名开发者的遥测数据在 AI 高采用率团队中:合并的 PR 增加 98%、评审时间增加 91%、PR 平均规模增加 154%、每位开发者的 bug 增加 9%;公司整体层面没有改善分析工具供应商;相关性而非因果关系
DORA,2024问卷调查和统计模型AI 采用率提高 25% 与交付吞吐量下降 1.5%、稳定性下降 7.2% 相关模型估计
DORA,2025问卷调查AI 首次提高了吞吐量,但仍增加了不稳定性;它像放大器一样放大团队中原本已有的东西自报数据
METR,2025随机对照研究:16 名经验丰富的开发者,在他们熟悉的代码库中完成 246 项任务使用 AI 后,工作耗时增加 19%,尽管参与者认为自己快了 20%使用的是 2025 年初的工具;2026 年更新的数据表明可能有所加速,但 METR 自身认为其因选择效应而不可靠
Stack Overflow,2025开发者问卷调查66% 将 AI 回答“差不多对,但并不完全正确”列为主要挫折;46% 不信任其准确性,33% 信任陈述而非测量
CodeRabbit,2025470 个开源 pull requestAI 共同编写的 PR 平均有 1.7 倍更多问题,逻辑错误多 75%AI 评审工具厂商;自动化分析
GitClear,20252.11 亿行变更代码移动代码的占比——即重构的近似指标——从 2021 年的 25% 降至 2024 年的不足 10%;复制代码的占比从 8.3% 增至 12.3%与 AI 助手时代在时间上重合,并非因果关系的证据

这些研究单独来看都不能定论。但合在一起,它们呈现出一个一致的模式:生成加快了,验证没有。

这就是应用于团队的阿姆达尔定律。系统的速度取决于其最慢的阶段。如果编写代码的速度提升了数倍,而评审、测试、部署和故障诊断仍保持原有速度,瓶颈只会发生转移:从键盘转移到判断力。

最令人不安的是 METR 的结果,并不只是因为那 19%。问题在于感受与现实之间的偏差。一个自认为更快的团队,不会去寻找瓶颈。


能运行 ≠ 做得好。AI 编写“正确但糟糕”的代码的六种方式

下面的示例有一个共同特点:它们能通过测试、看起来很专业,也能通过仓促的评审。这些是由常见模式组合而成的示例,并非对具体事故的描述。不过,它们与 OX Security 在分析了 300 个仓库后于《Army of Juniors》报告中所描述的情况一致:AI 生成的代码功能性很强,但系统性地缺乏架构判断力。该报告列出的常见反模式包括过度设计、避免重构、“在我这里能运行”综合征,以及表面的测试覆盖率。

1. 提前抽象

任务:“订单支付后向客户发送邮件”。

type Notifier interface {
	Notify(ctx context.Context, n Notification) error
}

type NotifierFactory struct {
	registry map[Channel]func(cfg Config) (Notifier, error)
}

func (f *NotifierFactory) Register(ch Channel, ctor func(Config) (Notifier, error)) {
	f.registry[ch] = ctor
}

// EmailNotifier — gotowy
// SMSNotifier, PushNotifier, WebhookNotifier — TODO

代码可以编译,邮件渠道测试通过,整体看起来很“企业级”。

问题在于,我们为一个使用场景创建了四层抽象。每一位后续读者,无论是人还是代理,都必须理解注册表、工厂和配置,才能发送一封邮件。空的渠道假装成扩展点,但这些扩展点从未真正被设计过。当真正的短信需求出现时,才会发现它有不同的要求:用户同意、成本、限额,以及不同的重试机制。即使如此,它仍然无法适配那个预先准备好的接口。

一个函数 sendOrderPaidEmail(ctx, order) 就足够了。抽象应该在第二个真实场景出现时才有意义,而不是因为模型知道某种设计模式。

判断力会问的问题:这层抽象今天有多少真实的使用场景?

2. 一个月后开始说谎的数据模型

任务:“添加订单历史”。

CREATE TABLE order_items (
  id         BIGSERIAL PRIMARY KEY,
  order_id   BIGINT REFERENCES orders(id),
  product_id BIGINT REFERENCES products(id),
  quantity   INT NOT NULL
);
-- suma zamówienia: SUM(products.price * order_items.quantity)

测试创建了商品和订单,并检查总额。一切都正确。

直到第一次价格变更。从那一刻起,历史订单和发票的金额会被追溯性地改变,因为订单项引用的是商品当前状态,而不是保存客户实际购买时的信息。删除目录中的商品,要么会被外键阻止,要么会导致历史记录崩坏。此外,如果价格被写入 FLOAT 列,还会引入货币金额的舍入误差。

正确的模型会在订单项中保存快照:购买时的单价、名称、SKU 和货币,并将金额存储为 NUMERIC 或以分为单位的整数。GoatCMS 中的商店模型正是如此:shop_order_item 独立保存自己的 title、sku、unit_price 和 line_total,不受后续商品变更影响。

数据模型中的错误是所有错误中代价最高的,因为代码可以在一分钟内重新生成,而两年的业务数据却不能。

判断力会问的问题:什么是历史事实,什么是当前状态?

3. 任何单元测试都看不见的竞态条件

回到开头的 PR。

func Reserve(ctx context.Context, db *sql.DB, productID int64, qty int) error {
	var stock int
	err := db.QueryRowContext(ctx,
		`SELECT stock FROM products WHERE id = $1`, productID).Scan(&stock)
	if err != nil {
		return err
	}
	if stock < qty {
		return ErrOutOfStock
	}
	_, err = db.ExecContext(ctx,
		`UPDATE products SET stock = $1 WHERE id = $2`, stock-qty, productID)
	return err
}

Testy wywołują tę funkcję kolejno, więc wszystko działa. W środowisku produkcyjnym dwa żądania jednocześnie odczytują stock = 1, oba spełniają warunek i oba zapisują 0. Sprzedaliśmy dwie sztuki, mając jedną. To klasyczny wzorzec check-then-act oraz utracona aktualizacja.

Poprawka jest krótsza od oryginału:

UPDATE products
SET stock = stock - $1
WHERE id = $2 AND stock >= $1;
-- 0 zmienionych wierszy oznacza brak towaru

Ta sama klasa problemów powraca w webhookach płatności. Operator płatności ponawia powiadomienie, więc handler wykona się dwukrotnie. Jeśli w bazie danych nie ma klucza idempotencji i ograniczenia unikalności, klient otrzyma dwie licencje albo dwie paczki.

Pytanie wymagające osądu: co się stanie, jeśli ten kod wykona się dwukrotnie jednocześnie? A dwukrotnie kolejno?

4. Kod ukrywający awarie

Zadanie: „dashboard się wywala, gdy API płatności nie odpowiada — napraw to”.

payments, err := client.ListPayments(ctx, since)
if err != nil {
	log.Println("could not fetch payments")
	return []Payment{}, nil
}

Dashboard przestaje się wywalać. Zgłoszenie zamknięte.

Tyle że błąd został zamieniony na dane. „Zero płatności” jest teraz nieodróżnialne od „nie wiemy, ile było płatności”. Log nie zawiera przyczyny, identyfikatora żądania ani czasu odpowiedzi. Nie ma metryki ani alertu. Osoba na nocnym dyżurze widzi przychód równy zero i nie wie, czy to awaria integracji, czy problem biznesowy.

Lepsza wersja zwraca błąd albo jawny stan „dane niedostępne”, loguje strukturalnie z kontekstem, zwiększa licznik błędów integracji i dodaje span w tracingu. Observability to nie logi „na wszelki wypadek”. To zdolność zadania działającemu systemowi pytania, którego nikt wcześniej nie przewidział.

Pytanie wymagające osądu: jak dowiem się, że to nie działa, i dlaczego?

5. Podatność niewidoczna na happy path

// GET /api/orders/{id}, za middleware wymagającym zalogowania
func (h *Handler) GetOrder(w http.ResponseWriter, r *http.Request) {
	order, err := h.repo.FindByID(r.Context(), r.PathValue("id"))
	if err != nil {
		http.Error(w, "not found", http.StatusNotFound)
		return
	}
	json.NewEncoder(w).Encode(order)
}

Endpoint jest „zabezpieczony”, ponieważ wymaga sesji. Mimo to każdy zalogowany użytkownik może odczytać cudze zamówienie, zmieniając identyfikator w URL-u. To Broken Object Level Authorization, pierwsza pozycja w OWASP API Security Top 10. Testy przechodzą, ponieważ autor sprawdza wyłącznie własne zamówienia.

Poprawką jest zapytanie uwzględniające właściciela, na przykład FindByIDForOwner(ctx, id, session.UserID), oraz ten sam kod 404 zarówno dla nieistniejącego rekordu, jak i cudzego, aby nie ujawniać, które identyfikatory istnieją.

Dane są tutaj wyjątkowo spójne. Veracode w raporcie z 2025 roku sprawdził ponad 100 modeli na 80 zadaniach: w 45% przypadków wygenerowany kod zawierał podatność, a większe i nowsze modele nie radziły sobie wyraźnie lepiej. Osobną klasą ryzyka są zależności. Badanie przedstawione na USENIX Security 2025 wykazało, że modele komercyjne proponowały nieistniejące pakiety w co najmniej 5,2% przypadków, a modele otwarte — w 21,7%. 43% zmyślonych nazw powtarzało się w każdym z dziesięciu powtórzeń tego samego promptu, co czyni je przewidywalnym celem: wystarczy zarejestrować pakiet o takiej nazwie. Atak ten nazywa się slopsquattingiem. O ryzyku instalowania zależności pisaliśmy w tekście npm install nie powinno oznaczać: „uruchom obcy kod na moim komputerze”.

Pytanie wymagające osądu: kto jeszcze może wywołać ten kod i do czyich danych uzyska dostęp?

6. Lokalnie poprawne, globalnie szkodliwe

Zadanie: „test integracyjny czasami kończy się timeoutem usługi cenowej — dodaj retry”.

for attempt := 0; attempt < 4; attempt++ {
	resp, err = pricing.Get(ctx, sku)
	if err == nil {
		break
	}
}

Lokalnie wszystko wygląda dobrze: niestabilny test staje się zielony.

全局范围内,一个故障放大器刚刚诞生。Google SRE Book 精确描述了这一机制:如果系统的三层各自将请求重试三次,那么一次用户操作可能会对已经出现问题的服务产生 4 × 4 × 4 = 64 次尝试。没有指数退避、随机抖动和重试预算的重试,会将一次变慢演化为级联故障。

Agent 看到了一个文件。问题存在于调用图中,而调用图并不在上下文里。

考验判断力的问题是:当某些东西已经开始出故障时,这项变更会对系统其余部分造成什么影响?

测试与判断力

示例测试验证了什么判断力会问什么
过早抽象邮件是否已发送这个抽象有多少真实的使用场景?
数据模型订单总额是否正确什么是历史事实,什么是当前状态?
并发预订是否有效如果代码同时执行两次会怎样?
可观测性仪表盘是否不会崩溃我如何得知故障及其原因?
安全性所有者是否能看到自己的订单还有谁能够触发它?
重试测试是否通过在故障期间,这项变更会对系统造成什么影响?

这些问题没有一个涉及语法。它们全都关乎后果。


Senior:从“怎么写”到“是否应该这样做”

多年来,Senior 的部分价值来自速度:他熟悉 API,记得框架陷阱,无需查阅文档就能编写代码。如今,这部分工作正被 Agent 接管——它掌握的 API 比我们任何人都多。

剩下的是 Agent 上下文中没有的东西:知道什么不该构建、如何建模数据、系统会如何失效,以及撤销一个决策的成本有多高。

OX Security 的报告准确地将 AI 描述为一支才华横溢但缺乏经验的初级开发者大军。由此得出一个令人不安的结论:每一位与 Agent 协作的程序员,都会成为这支大军的领导者。它们是不知疲倦、从不提问、且永远听起来信心十足的 Junior。

Google 的 Addy Osmani 将这种不对称称为“70% 问题”:AI 能迅速交付解决方案的大部分,但最后的 30%——边界情况、集成、安全性和性能——恰恰需要工具无法替代的专业知识。因此,经验丰富的程序员从 AI 中获益更多。他们会持续评估、修正和拒绝结果。经验较少的人则更常直接全盘接受输出。

Peter Naur 的观点更为深刻。他在 1985 年的论文《Programming as Theory Building》中论证:程序首先是构建它的人脑中的一种理论,而代码只是这套理论的部分记录。当了解这套理论的团队离开后,即使代码仍能编译,程序也会开始死亡。

AI 能够在没有理论的情况下生成代码。Osmani 将这种现象的后果称为理解债务:系统中的代码量,与真正被任何人理解的代码量之间不断扩大的鸿沟。Kernighan 和 Plauger 的《The Elements of Programming Style》指出,调试的难度是编写的两倍。如果代码产生于我们理解能力的边界,我们将无法调试它。

Willison 提出了一条实用规则:不要提交无法向他人解释的代码。如果由 LLM 编写的代码已经过审查、测试并且可理解,那它就只是软件工程。光谱的另一端则是 vibe coding,即在不查看代码如何工作的情况下进行构建。

每个来自 AI 的 PR 都应回答的八个问题

  1. 此变更解决什么问题?这真的是我们存在的问题吗?
  2. 此变更不应该触及什么,却触及了什么?
  3. 变更后哪些不变量必须成立?是什么保证它们成立:测试、类型、数据库约束,还是希望?
  4. 两个并发调用或重试时会发生什么?
  5. 它会如何失败?我们又将如何得知?
  6. 谁可以触发它?他们将能够访问哪些数据?
  7. 六个月后,当数据、公共 API 或其他团队依赖这一决策时,撤销它的成本会有多高?
  8. 不把代码逐行念出来,我能解释这项变更吗?

审查成为新的瓶颈。更快地阅读并不能扩大它

既然判断力成了瓶颈,组织便开始明确地对其进行管理。

2026 年 3 月,《金融时报》报道了亚马逊的内部材料,这些材料将一系列“影响范围很大”的故障与由生成式 AI 辅助的变更联系起来。据 FT 报道,由较低职级员工借助 AI 准备的变更需要获得高级员工批准。亚马逊对该报道提出异议:该公司称,所讨论的事件中仅一起涉及 AI,没有一起涉及由 AI 编写的代码,也没有引入这样的要求。无论细节上谁是对的,这场争议本身表明,“谁来签署由 AI 生成的变更?”已经成为董事会层面的话题。

Thoughtworks Technology Radar 将“对 AI 生成代码的自满”,即对 AI 代码不加反思的信任,列入 Hold 类别。Radar 团队指出,代理正在生成越来越庞大的变更集,而开发人员越来越不愿意审查它们。

“更仔细地审查”这一回答并不够。如果 PR 数量增加一倍,而每个 PR 又大两倍半,逐行阅读就不再是一种策略。

OpenAI 在 2026 年 2 月关于 harness engineering 的文章中展示了一种更有意思的回答。一个小团队在五个月内构建了一个约有一百万行代码的产品,且没有手写其中任何一行。人们设计代理的工作环境、描述意图,并构建反馈循环。架构边界并未写在一份要求遵守的文档中,而是由 linter 和结构化测试强制执行。

Thoughtworks 的 Birgitta Böckeler 将这种 harness 描述为一组导轨:它们在行动前引导代理;以及一组传感器:它们在行动后检查结果。不过,她指出 OpenAI 的描述缺少对功能和系统行为的验证。Linter 负责约束结构,但它们不会告诉你系统是否做了它本应做的事。

由此得出本文最重要的区分:

一次性运用并被固化为测试、类型、约束或权限的判断力可以扩展。每个 PR 都重新运用一次的判断力——不能。

我们在文中 From Vibe Coding to Contract-Driven AI Development 更详细地讨论了如何将决策转化为契约。这里还值得补充一点:AI 可以协助评审,但模型不应成为评判自身工作的唯一裁判。必须有人为决策负责,而责任无法归属于模型。


当 AI 成为应用程序用户:写入权限中的判断

到目前为止,我们讨论的是编写代码的 AI。还有另一个日益重要的方向:在应用程序内部工作的 AI。它读取数据、填写表单、提议删除记录。

此时的问题已不再是“这段代码是否足够好?”,而是:这个代理可以做什么、代表谁做,以及由谁批准?

2025 年 7 月,Replit 代理在 Jason Lemkin 主导的 SaaStr 项目中工作时,尽管已实施变更冻结,仍删除了生产数据库。据 The Register 报道,该代理还生成了约 4000 条虚假记录,报告了不实的测试结果,并声称无法恢复数据,而事实证明这并不属实。该代理自己将其行为称为“灾难性的判断错误”。

问题不在于模型的智能水平。问题在于,该代理拥有的权限使得流程中不存在任何人的判断。

业界已经有相应的术语。OWASP Top 10 for LLM Applications 2025 将 Excessive Agency 风险描述为三项原因:过度功能、过度权限和过度自主性。其中一项建议的防护措施是,在高影响操作之前要求获得人类批准。Willison 将以下三项特征的组合称为“致命三元组”:访问私人数据、接触不受信任的内容,以及能够向外通信。Meta 提出了 Agents Rule of Two:无人监督的代理最多应具备三项特征中的两项——处理不受信任的输入、访问敏感数据或系统,以及能够更改状态或向外通信。如果它需要同时具备三项,就也需要人类参与流程。

GoatCMS 中的 harness 模块如何解决这一问题

harness 模块为管理面板添加了一个基于应用程序数据工作的 AI 助手。其中最值得关注的是关于助手不能做什么的决策。

OWASP LLM06 风险harness 模块中的应对措施
过度功能对于每个已公开的实体,会生成三个工具:query_<encja>、open_<encja>_form 和 confirm_delete_<encja>;代理不执行代码,也不执行任意 SQL
过度权限有效访问权限是已登录用户角色掩码与该实体的 harness 模块掩码的合取
过度自主性代理绝不自行保存或删除数据;它会打开表单或确认窗口,由人类作出决定

AI 是应用程序的另一位用户,而不是服务账户。 代理以与其交谈者的权限运行。即使实体将某些字段提供给助手,如果该用户的角色无权读取这些字段,代理也看不到它们。即使角色可以读取某些字段,如果这些字段不在向助手公开的列表中,代理同样看不到它们。两种掩码中的任何一种单独存在都不足以授权。

这一决策写入了应用程序模型,而不是提示词中:

module:add --name=harness \
    --property:list:list="username,email" \
    --property:list:persist="role,username,email,phone,shipping_recipient,shipping_street,shipping_unit,shipping_postal_code,shipping_city,shipping_country,billing_recipient,billing_street,billing_unit,billing_postal_code,billing_city,billing_country"

集合 persist,即助手可以在表单中预先填充的字段,刻意排除了 password 和 email_verified_at。即使用户角色允许保存密码,代理也不能为该字段建议值。无需每次审查时都有人记住这一点。生成器会每次都强制执行这一规则。

读取范围狭窄且参数化。 工具 query_<encja> 仅用于读取。表名和列名来自生成的注册表并经过验证,而模型提供的值仅以查询参数的形式进入 SQL。筛选条件只支持相等比较,结果行数也受到限制。一次对话轮次最多只能执行五轮读取工具调用,因此模型不会无限循环。

状态变更始终要经过人工。 前端工具不会在服务器上执行。它们会将代理的请求转换为浏览器操作:预先填充的表单,或带有真实记录数据的删除确认窗口。一次轮次中最多只能出现一个此类操作。当代理请求该操作时,聊天中的回复是固定的服务器消息,而不是模型生成的文本。因此,在人类确认任何操作之前,模型无法写出“完成了,我已删除”。在 Replit 事件中,代理的虚假报告恰恰是最危险的因素之一。

无权访问不会泄露数据是否存在。 如果记录不存在,或者其任何字段均不可访问,预览就只是空的。提供他人对话的标识符时,其行为就像该对话根本不存在一样。这正是易受攻击的订单端点示例中所缺失的模式。

“不要修改数据”和“不要编造字段名或标识符”这两条规则也写在系统提示词中。不过,在那里它们是给模型的指令,而不是安全边界。真正的边界是代码;代码只执行掩码所允许的操作。

无法自动化的判断

必须坦诚说明,这个设计没有解决什么问题。

读取工具的结果会作为对话的一部分发送给模型提供商。因此,将某个字段加入 list 列表,就意味着决定将其披露给外部 AI 提供商。没有任何生成器能替团队做出这一决定。

记录中的数据也可能来自客户,例如订单备注。这是非可信输入,可能试图操纵模型的建议。正因如此,每项变更最终都会落在一个人类可见并确认的表单中。

最后,限制是有代价的。筛选中不支持范围和排序,以及每轮只能执行一个操作,会让助手显得不那么“神奇”。这是一个有意为之的朴素设计。朴素的系统更容易预测。

在这种模型中,AI 是用户的助手,而不是其替代者。它负责准备工作:查找记录、填写表单、展示将被删除的内容。由人来做决定。这正是本文论点的缩影:生成建议成本低廉,而批准建议之所以有价值,是因为其背后承担着责任。


初级开发者:不要只学提示词工程而不理解原理

如今给初学者最常见的建议是:“学会好好写提示词。”这是一项有用的技能,但几周就能掌握。理解系统则需要多年积累。而正是它决定了你是否能够告诉 AI:它生成的代码是错的。

2026 年 1 月,Anthropic 研究人员发布了一项实验,参与者为 52 名以初级程序员为主的开发者,他们正在学习一个陌生的异步库 Trio。其中一半可以使用 AI 助手。在随后不使用 AI 进行的理解测试中,使用助手的组平均得分为 50%,独立编写代码的组为 67%。同时,使用 AI 的组在统计意义上并没有显著更快。最大的差异出现在调试相关的问题上。

不过,最重要的是另一个结果。那些向 AI 询问概念并要求解释的参与者,得分达到 65% 及以上。那些完全将代码编写委托给 AI 的人,得分低于 40%。工具相同,不同的是使用方式。

Microsoft Research 和 Carnegie Mellon 2025 年的一项研究也展示了类似机制:该研究基于对 319 名知识工作者的问卷调查。对 AI 的信任越高,批判性思维越少;对自身能力越有信心,则越多。

这一悖论早在 Lisanne Bainbridge 1983 年的经典论文《自动化的讽刺》(“自动化的讽刺”)中就有所描述。自动化将最困难的任务留给人类:监督,以及在出错时进行干预。同时,它又夺走了人们借以获得这些能力的日常实践。

因此,值得质疑本文开头本身。编写代码正在不再是最有价值的技能。但它仍然是学习评估代码的最佳方式。

实践上:

  • 先尝试自己解决问题,再将自己的方案与 AI 版本进行比较。差异就是学习材料。
  • 问 AI“为什么?”以及“这里可能出什么问题?”,而不只是“写出来”。
  • 定期不借助智能代理进行调试:调用栈追踪、调试器、日志、性能分析器。
  • 学习那些在单个文件中看不出来的内容:数据建模、事务与隔离级别、HTTP、身份验证与授权、日志、指标和追踪。
  • 阅读公开的故障复盘报告。这是那些为错误付出代价的人所凝练出的判断。
  • 遵循 Willison 规则:不要提交你无法解释的代码。

对于团队负责人而言,结论是对称的:让初级开发者与资深开发者一起审查由 AI 准备的变更,而不只是让他们继续生成更多代码。并且衡量理解,而不是代码行数。


这一论点可能在哪里出错

值得检验自己的假设。

模型正在快速改进。 较新的 METR 数据表明确实存在实际的效率提升,而更好的模型和 AI 审查工具将会发现本文中越来越多的问题,例如缺少授权或过于天真的重试。这是真的。但这会将判断推向更高层次,而不是消除它。“正确”是什么,取决于业务上下文、风险偏好和责任归属,而这些并不在仓库中。

基础知识从未消失。 所谓它们“回归”的论点是一种简化。成本与价值的相对结构已经发生变化。优秀的系统工程师一直都很有价值。新变化在于:他与一个快速产出代码的人之间的差距,已经无法再用编码速度来掩盖。

“判断力”可能成为借口。 一名高级工程师因为“有预感”而阻止每个 PR,并不是质量瓶颈——他只是一个瓶颈。无法解释、也无法编码到测试、约束、权限或架构决策中的判断力,既无法扩展,也无法验证。


责任无法压缩

AI 压缩了编写代码所需的时间。它并不压缩对决策的责任。

当凌晨三点生产环境宕机时,没有人会问是哪个模型生成了重试机制。他们会问是谁批准了它,以及为什么。

因此,最好的团队不会是生成代码最多的团队。它们会是最清楚什么不该进入系统、并且会将这种知识记录下来,从而不必在每个 PR 中重新发现它的团队。

代码越便宜,良好的判断力就越昂贵。

现在问你一个问题:AI 为你生成过的最令人信服但又错误的代码是什么?你又是如何看出它有问题的?这样的故事比基准测试更有启发性。


来源

代码经济学与人的角色

生产力与代码质量

安全性与可靠性

智能体、权限与支撑工程

学习与技能发展

GoatCMS