# 从“写得快”到“验得起”——AI 时代的工程化重构(一) ## TL;DR - Agent 时代写代码不再稀缺,真正稀缺的是人的注意力,热点和瓶颈从 coding 发生转移。 - 一种有效的实践:**交替迭代**,测试和实现永远只改一边。用**确定性测试 + 快照测试**铸造迭代的**不动点**,把“上一个版本是好用的”固化成可回归的事实。 - 注意力预算管理:将测试分为 核心测试 (CT) 与 回归测试 (RT)。前者消耗稀缺的人类注意力确保“正确性”;后者消耗廉价的 token 确保“一致性”。 - 工程化新义:工程化不再是节制膨胀的“刹车”,而是 Agent 飞轮高速旋转时的“方向盘”。管理 Agent 的本质是管理“反馈闭环”与“上下文预算”。 ## “糙快猛” vs 工程化 trade-off 的消失 在软件开发中,我们始终面临两种范式的博弈。 一种可以称之为“糙快猛”,它推崇**快速行动,用最短的时间把想法变成可运行的原型**,甚至直接上线创造价值,在真实反馈中确定方向。 另一种则是“工程化”,它更看重**长期主义**,认为**代码的演进有路径依赖,初期的轻率决策会成为系统性的“技术债务”,导致后续代码在错误的基座上扭曲生长**,强调**规范、架构和组件化**。通过前置的抽象、解耦和标准化设计,严控系统熵增,防止项目膨胀至不可维护。这样确保项目完全受控,但代价通常是牺牲了迭代速度,而这往往意味着错失良机。 过去,开发者和团队常常需要在这两种倾向中做出权衡。Agent 的出现,给两种范式都引入了新的变化。一方面,过去的很多工程化方法聚焦于提高代码复用和节制代码膨胀,这难以追得上飞速发展的生产力。另一方面,当试图直接用 Agent 来“糙快猛”地开发新项目的时候,又因为没有亲手定义每一个函数,很快让代码库彻底成为一团不可知的混沌,完全丢失所有的掌控。 举例来说,目前很多人用 Agent 的手法就是照搬一个“交作业式”的 Agent workflow: - 整理好需求/spec,例如“开发一个数据获取框架,让用户可以从 /path/to/rawdata 读取 MarketData,要求实现功能 1 2 3 4 5” - 把 spec 发给 Agent 做实现 - 审核 Agent 实现的代码 - 然后做对比实验/回放验证,争取“敢上线” - 一旦发现问题,再回到上一轮循环 刚开始使用的时候,我们会体验很好,惊为天人。AI 在很短时间里生成了大量的代码,并且看起来还挺符合预期。但很快瓶颈会集中爆在三件事上: - Code Review 困难:审查不是自己亲手编写的上千行代码非常消耗心力 - 缺乏信心:即使 review 完,依然缺乏足够的信心去 fearless 地进行上线,可能还是要花费大量时间去做手动的对比实验来验 - 痛苦的返工:一旦在对比中发现了问题,又要**重新迭代**那个痛苦的开发-Code Review-测试流程 最后一复盘,往往会发现整体浪费的时间可能会比自己实现更久,最后一摔键盘,“垃圾 AI,浪费生命”。 **要想迭代快,关键不再是更快地产出代码,而是更便宜、更稳定地得到验证反馈。** **软件开发中,瓶颈永远在人的注意力**,当工程师从具体的编码工作解放出来,借助 Agent 低成本近乎无限地生成代码时,开发的瓶颈自然地从 coding 向其他工作转移。那么由过去聚焦于编码速度的 “糙快猛” vs 工程化的 trade-off 自然逐渐消失。问题变成,我们如何用工程化思维优化那些 Coding 之外,需要我们参与的部分,让他们不要阻挡 Agent 开发的飞轮? 过去的工程化是节制代码膨胀的刹车,而 Agent 时代需要的工程化,是作为高速迭代中的方向盘。**一个完善的测试框架、清晰的架构和明确的规范,不再是拖慢编码的流程,而是我们用来验证和约束 Agent 产出、实现真正意义上“快速迭代”的必要工具。** 本文就让我们从代码正确性开始。当你面对 Agent 在半小时生成的几千行代码时,你面临的不是“写不写得出来”的问题,而是“敢不敢用”的问题。如何优化开发飞轮中,最基本而又必须人来参与的部分:**我们如何用低验证成本确保 Agent 产出的成千上万行代码是正确的、可靠的、符合预期的?** ## 交替迭代:不再 review 全量代码,只看变化“残差” 交替迭代的底层逻辑非常简单:既然人的注意力是稀缺的,那我们就不去看代码本身,而去看代码变化的“残差”。实践上表现为,**测试和实现永远只改一边。** 它把“写代码”拆成两种模式,并让 Agent 在两种模式之间交替切换: - **模式 A:只动测试,不动实现。** 基于当前现有的工程,让 Agent 去完善测试框架、补充测试用例。此时实现代码尽量不动(最多只在接口层做少量必要调整,保证好 review)。 如果新增测试跑不过,默认不是“实现错了”,而是**测试没准确刻画现有行为**——让 Agent 自己把测试修到能稳定反映现状。 - **模式 B:只动实现,不动测试。** 基于现有测试框架做工程需求/性能迭代,目标是在完全不修改测试框架(最多只修改少量必要用例)的情况下实现功能并通过测试。 如果跑不过,默认就是**新实现有问题**——让 Agent 自己修到满足所有测试。 在这个过程中,开发者本人参与的部分依然很少——测试框架是 Agent 写的,测试用例是 Agent 添加的,功能也是 Agent 迭代的。开发者依然只 review 了部分关键测试样例和少量关键实现代码。这并不像传统 TDD(Test-Driven Development) 一样,开发者需要花费大量精力去做繁琐的测试,伤害 mental health。但从结果上来看,review 需要花费的心力大大减少,对代码的信心大幅增加。 那么这个方式的增益来自哪里?这套方法的巧妙之处在于,它为工程迭代引入了一个非常好的**不动点**。在我们的工程迭代过程中,我们可以永远沿用 **“上一个版本是好用的”** 这个关键信息作为“恒等映射”保证工程项目的可靠性和传递性,而开发者需要审核的仅仅是新功能的迭代和优化,作为 **“残差”** 的那部分。 将该原则具象化为以下两条约束: - **写测试的时候,应该保持代码不变**(或者只在接口层变动,非常好 review)。如果 Agent 写的测试跑不过,那说明是测试写错了,它就得自己改,直到测试能准确反映现有代码的行为。 - **写功能的时候,应该保持测试框架不变**,最多根据功能变更修改少量用例。如果 Agent 写的功能跑不过测试,那说明是新代码有问题,它就得自己改,直到新功能满足所有测试的要求。 这样,**Agent 就不再是一个只会“交作业”的代码生成器,而是一个可以在每个阶段自我修正的“开发者”了。** 接下来问题就变成:这个“不动点”怎么铸造?怎么让它覆盖足够多的情况、又足够稳定,同时还保持能高效迭代? ## 确定性与快照:迭代的“不动点” 上一章我们引入了一个关键概念:不动点。在交替迭代的 workflow 中,我们永远假设一件事成立——上一个版本是好用的,并以此作为下一次迭代的基准。 要让这个“不动点”真的可用,它至少需要满足两个条件: - 它必须覆盖到足够多的情况; - 它必须在版本之间足够稳定。 乍一看,这似乎又回到了传统工程里那些熟悉的词汇:分支覆盖率、功能覆盖率、测试完备性…… 问题在于:当测试本身的规模也被 Agent 放大之后,Code Review 成了新的瓶颈。大量测试即使交给 Agent 编写,人工逐条 review 依然极其消耗心力,严重损伤 mental health。这就引出了一个看似危险、但对“不动点”至关重要的结论: - **不动点并不要求“正确”,它只要求“行为在版本迭代上的连续性”。** 如果测试记录的行为本身就是正确的,那当然更好;但即便它并不完美,只要老版本是可用的,新版本至少不应该出现你没有意识到的 regression —— 这就已经把工程迭代的风险压缩进了一个可控范围。 这个观点在传统的软件工程叙事中并不“政治正确”。我们习惯强调交付结果的可靠性,而“允许存在未完全验证正确性的测试”,听起来像是在“让用户或者实盘帮你测”。但这里的关键并不是放弃正确性,而是**用廉价的 token “白嫖“ 功能迭代的行为连续性**。 在解释这套资源模型之前,我们先要引入两个好用的工具:确定性测试与快照测试 ### 确定性测试(Deterministic Testing) 这个词听起来很学院派,但它对这种“交替迭代” workflow 非常重要。我们**必须消除测试中的“噪音”**:如果输出本来就不稳定,你的 snapshot diff 只会把人逼疯。 在单线程程序中,这相对简单,主要关注几点: - 固定 random seed - 注意 hashmap 这类无序数据结构的遍历顺序(往往内置了一个 random seed) - 将获取系统时间戳之类的接口“hook”在最外层,作为输入参数传入 但在多线程程序中,这事儿会变得非常复杂,这里不过多展开,有兴趣的可以看一下 [Foundation DB 的文章](https://apple.github.io/foundationdb/testing.html),或者 madsim、miri 这样的库。 ### 快照测试(Snapshot Testing) 在保证测试程序有了确定性的输出之后,Snapshot Testing 的作用就是快速地把测试的输出结果 dump 下来并保存到 Git 与测试的结果对齐。快照的输出可以是非常大量的、难以肉眼验证正确性的,比如一整天某个标的每秒钟的 mid price,每分钟的 VWAP,在指定信号流下某个策略输出的下单流等。 在 Python 中我们可以非常简单的用 [syrupy](https://chatgpt.com/s/t_6969b3219c748191b2f7513d1cf1afde) 无脑引入一堆 Snapshot Testing(已经投入实践)。核心目标是,只要代码上**我们关心的行为发生了任何变化**,包括数值异常、顺序变化、类型调整,都一定要影响到输出的结果。 ```python def test_mid_price(snapshot): df = db3.read("2025-12-01", "bnuswapall_1s_mid_price") assert df == snapshot # 或者嫌弃输出实在太庞大的话 assert df.describe() == snapshot assert df.shape == snapshot assert df.dtype == snapshot assert (df.index.min(), df.index.max()) == snapshot ``` ### 用快照构建回归测试(Regression Testing) 现在可以回答这章开头的问题了,如何用有限的注意力管理海量的测试?答案引入就是将 Snapshot Testing 作为**回归测试**(下文简称 RT)的实现方式。 回归测试由 Agent 大量生成,**开发者可以不 Code Review 测试的行为**: - 目标不是证明"输出一定正确",而是把当前版本的行为固化成基线; - 后续任何改动只要让行为发生变化,**就一定会在 diff 上暴露**; - 人只在 diff 出现时介入,判断"这次变化是否符合预期/是否可接受"; - 回归测试消耗的**仅仅是 token**:让 Agent 多跑、多 dump、多存档;**开发者的注意力投入接近 0**。 具体实践模板包括: - 大输出回放的统计快照:对一天内某标的每秒 mid price / 每分钟 vwap 等,snapshot df.shape、dtypes、describe()、分位数等摘要。不需要证明这些摘要"绝对正确",但只要发生"悄悄变坏"的 drift,diff 一定会抓住它。 - Schema / 接口契约快照:对外 API 返回的字段集合、类型、空值约束、错误码等做 snapshot。这类变化通常比数值变化更致命,Code Review 非常适合兜底。 - 边界输入的行为连续性:让 Agent 枚举大量 edge cases(空数据、极端区间、乱序、重复 key、缺失字段、非法编码……),记录"输出摘要/错误类型/日志摘要"的 snapshot。不追求"正确",只确保行为变化一定会被 diff 提醒,你再决定要不要把这个 case 升级成核心测试去严格定义正确性。 RT 的根本的目的是,让 Agent 从“**上一个版本还不错**“这个事实中获取信息。通过快照测试,我们将这个信息固化下来,Agent 的每次修改,都必须以不破坏这个“不动点”为前提。**如果快照有变动,那么这个变动就是我们上一章所说的“残差”**,是我们需要重点审查的部分。 ## 并行核心测试(Core Testing)与回归测试(Regression Testing) 通过上文的方法,我们用近乎零注意力成本的方式构建了大量回归测试,但我们依然要回答本章开头提到的另一个问题:**测试都不关注正确性了,那我们的交付质量谁来保证?** 这个问题的回答非常简单,那就是继续**保留“需要关注正确性的测试“**,我们这里称之为**核心测试(CT)**。CT,简而言之,就是原来该怎么测试现在还怎么测试: - 人工挑选最值得测试的关键路径/关键行为 - 输出尽量"小而清晰" - 编写测试可以交给 Agent,但**开发者一定要确认 expected output 的正确性** - **CT 消耗的关键资源就是注意力:你要看它"对不对"。** 既然 RT 的构建过程完全不消耗注意力,那我们可以保证**引入 RT 完全不会影响我们在 CT 上的资源投入**。那么我们就回答了这个问题:**用 CT 保证了不低于传统开发 workflow 的正确性,而用 RT “白嫖“到了功能迭代的行为连续性。** 另外,一个实用的实践是:一旦 RT 中的某个 diff 反复出现,说明它测试的行为是经常改动的关键路径,很值得被就"提纯"为 core test(补上语义断言 + 人工认可的 expected output)。 **注意力永远花在刀刃上** ### 工程上怎么区分这两类测试? 实践上不需要很复杂,最朴素的约定就够了,关键是**让“review 与否的约定”可执行**: - **目录分层**: - `tests/core/`:core behavior tests - `tests/regression/`:regression tests(快照为主) - 迭代时,两类都跑,但对于变更的 review 规则不同: - core:PR 必须人工确认断言语义/expected output; - RT:PR 不要求逐条阅读 baseline,只在 snapshot diff 出现时人工判断“变更是否合理”。 有一个小的误区需要澄清一下:Snapshot Testing 只是一种工具,不是专为 RT 构建的。 - CT 中一样可以使用,只要你**认真看了生成的 snapshot 是否是正确的**。 - RT 中也不一定要使用,比如你关心的运行覆盖某个分支的代码之后只要不 panic 即可。 ## 工程化思维,而非工程化实践 我们在开头强调了工程化是 Agent 时代快速迭代的必要前提,我们在上文大量引用了工程化的很多经验。但需要再次强调的是,我们要建立的是 Agent 时代的**工程化思维**,而不是强行复用传统的**工程化实践**。相比于传统的工程化实践,我们的 workflow 会有大量“离经叛道”的操作(例如忽略测试的正确性)。工程化思维的本质,是对于一个长期维护的项目,在考虑了各种维护操作(如功能实现、性能优化、文档沉淀、组件重构、测试完善等)会使用到的各种共享资源(如开发带宽、团队 mental health、机器成本、维护负担、Review 成本等)之后,为了完成一系列目标(交付速度、交付质量、扩展可能性等)得到的一些相对较优的实践。 传统工程化实践的问题在于,它背后的成本模型在 Agent 的影响下已经有了很大的变化: - **coding 成本**:变得几乎可以忽略不计,且带宽大得可怕。以前,代码是稀缺资源,即使是算法高手,产生大量代码也需要消耗很多心力。现在,Agent 可以在几分钟内产出几千行代码。 - **测试/文档的编写和维护**:变得不再枯燥乏味,不会对开发者造成很大的心智负担。以前,很多工程师讨厌写测试和文档,因为这不仅枯燥,还需要在功能改动时保持最新。现在,我们可以让 Agent 去做这些事情,人类只需要验证它们的质量。 - **Context**:成了真正的稀缺资源。模型的 context 窗口是有限的,如何在有限的 context 中放入最关键的信息,成了新的挑战。 - **协调者的思考时间**(包括 Code Review):成了稀缺资源。一个人能同时协调多少个 Agent?一个人能在多少时间内做出高质量的决策?这成了新的瓶颈。 - **Prompt**:成了稀缺资源。一个好的 prompt 或者 prompt workflow 需要精心设计,需要对问题有深入的理解。这需要人的思考和创意。 我们可以画一张图来理解这个新的资源模型。 ![1](./1.png) 我们可以感受到,跟开发者相关的交互都是绝对的瓶颈: - 精心设计 Prompt - 阅读 Agent 给出的变更解释 - Code Review 项目的变更代码 - 为项目或 feature 做架构设计 为此,**我们的“工程化实践”必须是基于这种新的资源模型下重新建立的**。 最后,我想强调一点:我们倡导工程化作为手段,但像以前的“糙快猛”一样能实现**快速迭代**才是我们的目的。我们不是要像传统大厂那样,一个项目设计几个月,开发几个月。我们倡导的工程化只是手段。因为在 Agent 时代,**工程化反而成了快速迭代的最佳方式**。 ## 结语:工程化在 AI 时代到底“管什么” 写到这里,其实整篇文章只想把一件事说清楚:Agent 让 **Coding 带宽** 变得几乎不稀缺,于是开发的主战场从“写得快”转向了“验得起”。工程化在这个时代的意义,也就跟着变了。 很多人会把瓶颈归因到 Code Review:Agent 像风一样产出几千行代码,人当然看不过来。但这更像误诊——在很多严肃场景里(比如策略迭代),真正最费时间的往往不是 review,而是为了“敢上线”去做的对比实验、回放验证、以及线上问题的定位成本。**验证才是最贵的那一段。** 所以我更愿意把“用 Agent”当成一个管理问题:你不是在管理它写了多少代码,而是在管理三个东西: - **反馈闭环**:交替迭代 + 不动点,让 Agent 在边界内自我修正;让“上一个版本是好用的”变成可以自动回归的事实。 - **稀缺资源的预算**:prompt、context、人的注意力、以及验证周期。token 不是瓶颈,人脑才是。 - **长期记忆**:把一次协作沉淀成仓库里的文档、`AGENT.md`、Skill,以及那些看起来很不工程化的 ad-hoc 脚本(它们往往是最真实的 use case)。 由于篇幅限制,我们在这篇文章中重点探讨了**反馈闭环**在 Agent 开发中的意义。有机会我们可以进一步分享另一个 Agent 工程化中的重点:Context 和 Memory 的管理。 最后回到标题:Agent 不是代码生成器。它更像一个执行者,工程化也不再是“慢”的代名词——工程化是在 AI 时代让你敢让它跑起来、并且跑得不摔跤的那套护栏。 **每个人都需要成为一个合格的 Agent 管理者。** ## 附录 1:MDR:把流水线跑顺的一次实践 理论说了这么多,让我们来看一个完整的实战案例:一个新的 tick 数据读取框架(mdr)的开发过程。**这个项目从 0 到 1,完整地展示了如何在 Agent 时代进行工程化开发。** **在这个实战中,我不会贴任何的 Prompt。**Prompt 并不是重点,重点是我们如何构建这个开发 pipeline,让任何基本合格的 Prompt 都能达成我们目标的开发需求。 实践是非常不适合在文章中展现的形式,因此作为附录简单展示一下,我会尝试在每一步注明我们这么做的目的,以及我们收获了什么。 ### 让数据先跑起来:准备必要的信息获取脚本 首先,我们准备好数据存放的位置,并让 Agent 实现一个超简单的 Python 脚本来读取原始文件。这是一个 "hello world" 级别的实现,目的是确保我们能够访问和理解数据。 Why:一个合适的数据获取脚本,可以让 Agent 自动帮我们抓取很多数据的细节信息,而手工整理这些信息是非常繁琐的工作,非常消耗心力资源。 **收获:一个 Demo 脚本,可以读取任意时间段的数据,查看数据存储格式、数据类型、数据信息。** ### 编写初始文档:最小的人工准备工作 然后,我们撰写需求文档,明确以下几点: 1. 我们试图解决什么问题?(老框架的瓶颈是什么?) 2. 老框架有什么现有的问题?(性能?可维护性?API 设计?) 3. 有哪些现成的资源可以使用? 4. 数据在哪里?如何访问? 这个需求文档不需要非常详细,过于详细反而会浪费 review 的注意力,但必须清晰,且经过完善检验。它的目的是为后续的架构设计奠定基础。 **收获:一个最小的需求文档** ### 用“思考模型”打磨架构:从 high-level 到可执行 prompt 这一步是最关键的。我们与 ChatGPT Pro Thinking(一个强大的思考模型,但只能在网页上使用而非 API)进行了若干轮沟通。 首先,我们提供需求文档,让它阐述对需求的理解,并提出缺少哪些关键信息能辅助它决策。这一步很重要,因为它能帮助我们发现自己想法中的漏洞和不清晰的地方。 然后,我们补充信息,并根据自己的 sense 添加一些人为引导。比如,出于性能考虑,我们希望用 Rust 实现而不是 Python。这个引导很重要,因为它能帮助模型在多个可能的方向中做出选择。 接下来,我们要求它先做一版 high-level 的架构设计,比如拆分成哪些组件/layer,每个组件/layer 负责什么。这个版本应该是相对简洁的,能在一两页纸上表达清楚。我们进行评审、讨论、修改。 然后,我们要求它做出更细节的架构设计,比如项目的组织方式、每一层的关键类、负责维护的状态、关键 API 的 signature 和行为。这个有点像传统软件开发中的 UML。这一步产生的文档会相对较长,但非常具体。 最后,我们要求它根据它输出的架构,输出一版给 Agent(Codex)使用的 prompt。这个 prompt 会要求 Agent 创建好项目的整体框架,建好对应的文件、关键类、关键方法,但在实现中留白。这个 prompt 本身就是一个很好的 "协调指令" 的例子。 **收获:一个完善的设计文档,以 codex prompt 的方式呈现** ### 先搭框架、先让它编译:把 build 当成第一条反馈信号 进入工作目录,我们开始跟 Agent 对话。这里有一个可能有用的 trick:先选择一个你觉得维护状态良好、技术栈相似的模板项目(对于 mdr,我选择了 `apache/opendal`),把它的路径放在 prompt 的开头,告诉 Agent 可以借鉴这个 repo 的工程实践。 我猜测这个 trick 有用的原因是,LLM 学习的知识包括各个框架各个版本的内容,不同版本的 best practice 会有污染。在 context 中添加一个模板项目,可以强化 Agent 对当前版本最佳实践的认知。 然后,我们把 ChatGPT Pro Thinking 输出的 prompt 交给 Agent,等待它完成框架搭建。我们要求它编译通过,这是一个很好的 "反馈信号"——如果代码不能编译,就说明框架有问题。最后,让它根据实现生成 `README.md` 和 `AGENT.md`。(Dump Memory) **收获:一个可以通过编译的脚手架** ### 构造一个客观事实作为正向反馈:e2e integration test 对于行情数据的获取,客观事实就是应该尊重我们已知的行情信息。我从现有的因子中捞出了一些标的的每秒 mid price 等信息,然后要求 Agent 实现一个测试,确保基于新框架读取数据计算的 mid price,与我们已知的事实完全相等。 这是一个非常具体的、可验证的需求。Agent 可以清楚地知道什么是 "正确" 的。 我还构建了一些其他的测试来覆盖 depth 和 trade 混合读取、跨标的读取等需求。这种端到端的集成测试,比传统的、经过 monkey patch 的单元测试能测到更多东西,是我更喜欢的工程实践。 **收获:使用 Ground Truth 做了一个非常强的正反馈信号** ### 让实现追着测试跑:完成核心实现 要求 Agent 填充接口中的实现,以通过上面编写的测试。然后进行简单的代码 review。这一步应该相对快速,因为框架已经搭建好了,Agent 只需要填充具体的逻辑。这一步会花费相当多的时间,但开发者需要做的事情反而非常少。因此没什么好说的。 **收获:通过 e2e integration test 的一个正确实现** ### 用快照扩展覆盖面:把行为连续性固化下来 在完成了核心实现之后,我们又可以让 Agent 继续迭代整个测试框架,为后续开发提供更多的正反馈。 比如,要求 Agent 大量生成快照测试。例如,每天每小时的订单簿快照,或者把接口的输出构建成 pandas dataframe 之后,dump `df.describe()` 的结果,并多次运行,保证结果是稳定的。这些快照测试不需要人工验证,只需要在后续的迭代中,如果快照发生变化,我们才需要人工检查。(本质上就是在铺 **continuous regression testing** 的覆盖面。) **收获:基于当前正确行为,尽可能覆盖了所有行为的迭代连续性反馈** ### 给性能也装上闭环:benchmark + flamegraph 要求 Agent 根据当前的测试,构建一些性能测试,并且能很方便地被 `cargo flamegraph` 调用。比如读取每天前 20 分钟的数据,回放订单簿等。 这一步的目的是建立一个 "性能反馈" 的机制。Agent 可以通过运行 benchmark,看到具体的性能数据,然后做出优化决策。 **收获:构建了一个好的性能正反馈函数** ### 优化也要可回滚:收益不够就撤 这一步是最有趣的。我们要求 Agent 运行 benchmark,生成 flamegraph,并提出可能的性能优化点。 对于每一个优化点,我们要求 Agent 按照以下流程进行: 1. 尝试实现这个优化。 2. 先通过上面生成的行为测试,保证没有修改行为。 3. 再运行 benchmark,确认是否有可观的性能提升。 4. 如果有可观的性能提升,简单 review 代码,并提交。 5. **如果没有可观的性能提升,要求 Agent 回滚代码。** 为什么要回滚?因为性能优化往往会增加代码复杂度,进而增加 context 占用,必须要用在刀刃上。一个没有明显收益的优化,反而会增加项目的维护成本。 **收获:基于前面构建的正确性反馈、连续性反馈和性能反馈,自动获取了一个高性能的正确实现** ### 收尾与沉淀:把短期记忆写进仓库 最后,我们要求 Agent: 1. 生成对应的中文文档。可以给一个自己喜欢的文档模板,让它学习文风。 2. 在 `AGENT.md` 中记录重要的 knowledge,如果过长的话可以拆分一些 docs。 3. 在 `AGENT.md` 中记录哪些信息是对下游用户可见的。这很重要,因为这个信息可以用于辅助生成 `SKILL.md`。 4. 生成安装脚本/安装指南,记录在 `README.md` 中给后续的开发者看。 5. 生成 `SKILL.md`,给下游调用者的 Agent 学习库的用法。 开发者按顺序 review 并确认以上流程,然后执行发布。 **收获:沉淀这轮迭代的重要知识,同时给用户输出当前的稳定版本(客户觉得好用也是连续性反馈的重要来源),为下一次需求迭代做准备** ## 附录 2:语言选择:为什么是 Rust 相比 C++ 是 Agent 时代更好的选择? ~~这一段是纯私货~~ 在讨论工程化思维时,还有一个有趣的观点值得提出:在 AI 时代下,**Rust 是比 C++ 更优的高性能编程选择**。这个判断其实跟正文的主线关系并不算强,更像是一种时代背景下的工程直觉,但我还是很想把它写下来。 **如果连编辑器都不用打开,从“工程反馈”的角度看,不同编程语言最大的区别是什么?** 答案并不是语法、生态(当然生态也很重要),而是:**语言和编译器能不能给实现者提供足够强、足够早的正反馈**。 Rust 编译器在这件事情上非常激进,可以说是有实际工程应用价值的语言里最激进的。很多人会觉得 Rust 的编译检查“限制发挥”,但从另一个角度看,它本质上是在给你一个非常明确的奖励函数: **写错了,就别想编译通过** 这件事在 Agent 时代变得格外重要。因为 Agent 本质上并不是一个“值得信任的实现者”。你可以把它理解成一个能力极强、速度极快、但**不具备责任感、也不具备全局安全意识的超级实习生**。在这种前提下,工程系统本身就必须承担更多“兜底”的职责。 Rust 在这里的优势,并不只是“更安全”,而是它非常清晰地解决了模块与模块之间的**信任边界**问题。通过 Ownership、Borrowing 这些看起来比较难懂的规则,你可以把“哪些代码有权修改哪些内存”“哪些资源只能被消费一次”“哪些引用不可能越界”这类约束,直接编码进类型系统里。 我早年做的一个[分享里举的例子](https://rustpre.vercel.app/37),讲的是 Rust 的 Ownership 机制如何在**必须 zero-copy、必须 inplace-write 的高性能路径**上,通过类型系统实现的架构设计,可以非常放心地让实习生、外包、或者未经充分验证的开源贡献者去写具体实现,而不用担心“一行错误代码把整个系统打崩”。 现在,这个“实习生”变成了 Agent,这个问题只会变得更尖锐。 这种场景在真实的高性能系统里并不少见。比如计算图里的表达式框架,常常会有几百个表达式实现;又比如我之前在一次分享后,和一位自动驾驶厂的 Leader 交流过,他们的规则检查框架里有**几千条 C++ 规则**,而这些规则运行在一条极其关键的性能路径上。他们非常清楚,如果能优化掉某一个关键的 copy,性能可能立刻提升 20%~30%。但现实是,他们完全不敢动。原因也很简单:只要有任何一条规则在实现时没有严格遵守内存写入的约束,就可能造成内存溢出,轻则 segfault 车机宕机,重则静默内存错误,车辆行为进入不可知状态。而编译期限制不同模块对内存的访问,从而进行 fearless 高性能优化,正是 Rust 的甜点区。 好的模块边界对 Agent 时代的开发带来的收益是什么?**可以将稀有的注意力集中在整体的框架设计上,更少关注大量的局部实现,同时也不会引入更高的风险。** 当然,这个选择成立的前提是这个项目必须是高性能实现。否则,永远选择 Python。Python 对 LLM 来说就是更专家级的技能。这可能来自于更大规模的数据集,以及更接近人类语言的表达,以及大模型技术人员本身也大量使用 Python,会更关注 Python 效果,从而可能对模型迭代产生一定作用的原因。总之,任何工具都建议实现一层 Python wrapper,作为胶水层。 ## 致谢 - 感谢 ChatGPT 根据前期手稿帮忙提供文章整体组织思路,编写了初版文章(虽然最后被手工完整重写了)。 - 感谢 @tsshi 承担编辑职责,帮忙补充了文章的部分细节,润色了文章的连贯性。 - 感谢 @zqi @ycxu 周品良 作为 Beta 读者提供了大量宝贵修改意见。 - 感谢 kimi-cli 承担了最终审校工作,做了很多术语一致性/标点一致性上的改进。