再立浪潮之巅
当年做 Booster 的时候,我就想做一个基于 code graph 的数据流分析框架,可惜个人精力有限,一直搁着。年初,为了验证 Agent 扫描代码里残留的 AB 实验到底准不准,我需要一个能拿来对照的工具。于是趁一个周末,抱着试试看的心态,用 Opus 4.5 把它捡了起来。
Claude Code 只用了一个小时,从架构到测试全是它自己写的。这种工具我手写过,至少得花两周。搁了那么久的念头,就这么做出来了。可写了近二十年代码,我一直觉得,无论行业怎么变,这门手艺总还是靠得住的。现在它也会了,而且比我快这么多。以后如果不再需要我写代码,我还剩下什么?
接下来的两周,我疯狂地试探它的能力边界,想找到它做不到的地方。每试一次,留给自己的余地就又少了一点。在这个行业里,35 岁是一道大家心照不宣的坎。好不容易靠这些年攒下的经验和手艺跨了过去,回头发现,自己倚仗的东西也在变。
那种投入,后来连睡觉都没能打断。我在《植入潜意识——AI 版的盗梦空间》里记过,连续半个月高强度使用 AI 之后,原本不怎么做梦的我,开始每天晚上梦见自己在和 AI 对话,一连好多天。白天的对话到了梦里还在继续。我当时把这叫作“俄罗斯方块效应”。那阵子,我急着弄清楚,这场变化到底会把我带到哪里。
后来这三个季度,我几乎每天都在高强度地做 Agentic Engineering。那个问题一直跟着我,只是随着做的事情越来越深,我对它的理解也慢慢变了。
从写 Skill 到交付 Feature
最开始,我把自己处理一类任务的经验写进 Skill,告诉 Agent 从哪里查起、要注意什么、哪些步骤不能漏。后来开始搭 Agentic Workflow,把需求、实现、检查和交付接起来,让它把 Feature 一直推到 Production。
做到这一步,注意力很容易落在流程上:任务怎么拆,失败怎么重试,Context 怎么续上,下一轮怎样接着干。这些问题要解决,否则 Agent 做一半就停,人还是得一直守着。但当它能够持续推进,另一个问题就越来越明显:它说完成了,我凭什么接受?
编译通过、测试全绿,都有意义。可需求有没有理解错,测试是否只验证了它自己的理解,改动放进整个系统以后有没有破坏别的承诺,这些不会随着流程跑通而自动消失。实现越快,留给人的这部分工作反而越显眼。
假设十个 Agent 同时交来十份 diff,团队仍然靠同一个 Senior 逐行读完,产出越快,待验收队列就越长。让 Agent 多跑几轮,也不能替这个 Senior 判断该看什么。我在《别被 Loop Engineering 忽悠瘸了》里写过,持续执行和持续接近正确,是两件不同的事。
我在《还要工程师干什么?》里对实现型工作的替代判断,也没有因为这些问题而改变。Agent 已经能做的事情,不会因为我怀念亲手写代码就重新变得稀缺。只是我开始看到,在实现之外,还有一部分工作并没有被它一起接过去。
这部分工作,过去也一直在做,只是和写代码混在一起,不太容易单独看见。
流程跑通以后我把精力放到了哪里
Workflow 这一层,市场已经有很多选择。LangGraph提供持久执行、状态管理和人工介入能力,n8n也能把 Agent 和业务工具编排进工作流。这些产品各有边界,但有了现成能力以后,我就得问自己:继续把时间花在同类机制上,还是去解决它们没有回答的问题?
我的投入逐渐落到 Harness、Eval 和 Verification 上。Harness 为 Agent 提供上下文、工具、执行环境和反馈,其中的状态保存、工具接口、检查结果回传,也有不少通用做法。
我一直在 build 的 Graphite,就在补 Harness 中的代码上下文和分析能力。它从 JVM 字节码建立程序图,把调用、依赖和数据流变成可以查询的结构,再通过 MCP 交给 Agent。清理 AB 实验需要这种能力,其他理解代码和检查改动的任务也可以复用它。
回头看,年初为什么要把那个搁置的工具捡起来?就是因为 Agent 扫出了一些实验代码,我需要一个可以拿来对照的办法。它说“这些都找到了”,和我能确认“约定范围内没有遗漏”,中间还差一份证据。
但即使拿到了图,工作也没有结束。Graphite 能回答某个值流向哪些调用,不能仅凭这个关系决定业务上应该删掉哪一条分支。同一套工具可以用在不同项目里;项目允许删除什么、必须保留什么,仍然要重新定义。
到这里,Eval 和 Verification 的价值就具体了:Eval 要把任务、样本和判定标准定义清楚;Verification 要取得可信证据,判断具体产物是否满足要求。一个负责把“什么叫对”说到可以评估,一个负责确认这次到底做对了没有。
我在 Graphite 项目里,也把这个顺序落实到了开发过程:先定义 benchmark gate,再开始让 Agent 提 PR。但“怎样才算性能回退”,本身就需要判断。让 Agent 来定义,它会把注意力放在单图的 build、save、load、query 上。建图、保存、加载、查询都测到了,乍一看,完全没毛病。
我却不认可这套性能验收方式。Graphite 这些单图操作的绝对耗时本来就很低,运行环境一点点波动,换算成相对基线的变化,就可能出现一个很大的回退比例。报告上的数字变差了,究竟是改动让系统变慢,还是这次测量受了环境影响?如果连这件事都分不清,用它来决定一个 PR 能不能过,就可能让 Agent 围着噪声反复优化。这次 Agent 没有意识到,测完所有操作,仍然可能没有选对衡量性能的负载。
我定义的性能回退标准,是用多图、大图构造有代表性的压测负载,看压测条件下的 metrics,再据此判断改动是否可以接受。多图也要真正进入被测请求的工作范围,不能只是加载了好几张图,实际仍然只查其中一张。单图测试可以保留,用来检查正确性;性能验收则以这套压测为准。我要看的是系统承受这种负载时的表现,单次轻量操作的耗时变化回答不了这个问题。
Benchmark 的代码可以交给 Agent 写,选择什么负载、什么变化值得拦住一个 PR,却需要我对系统和测量结果作出判断。这件事让我更清楚地看到,过去做工程时积累的经验,正在变成 Agent 开始工作之前就需要的标准。
Eval 和 Verification 也有通用框架,例如 Braintrust 能运行现成和自定义的 scorer。我在《Harness 还是 Agent 的壁垒吗?》里讨论过这条边界:运行评测的机制可以采购,哪些样本代表这个业务、怎样的结果值得接受、什么证据足以支持交付,仍然需要具体判断。
真正值得我持续投入的,是 Eval 和 Verification 里那些必须理解业务与系统才能完成的部分。 这些问题至今没有一套开箱即用、跨业务成立的完整解法,也很难有。答案分散在系统约束、业务决定、历史事故和真实使用结果里;同一个输出,在不同的兼容性承诺和风险要求下,可能得到相反的结论。
我最初在找的是 Agent 还不会写的代码。沿着工作往下做,却越来越需要说清楚:为什么要做这件事,做到什么程度可以交,拿什么证明。再回头看工程师原来的职业路径,我才发现,这些问题一点都不陌生。
回头看那条职业阶梯
过去我也在这条路上走了很多年。工程师的职业阶梯,各家公司叫法不一,大体是 Junior → Senior → Staff → Principal → Distinguished。很多人把它理解成代码越写越好、问题越做越难。可沿着职责往上看,更明显的变化是:你要在越来越大的范围里,定义什么叫对,并对结果负责。
各家公司的具体边界不同。沿着这条主线,我把传统工程师的 R&R(Role & Responsibilities)概括成下面这张表:
| 职级 | 负责的范围 | 核心职责 |
|---|---|---|
| Junior | 已经拆好的任务 | 理解给定要求,完成实现,在既定标准下证明任务完成 |
| Senior | 一个功能或模块 | 独立澄清边界、选择方案,定义成功标准,并负责交付与运行结果 |
| Staff | 多个团队共同面对的问题 | 协调接口与约束,建立共同指标和反馈机制,让各团队的工作能组成一个结果 |
| Principal | 一个技术领域或业务线 | 确定技术方向,在业务目标、系统演进与长期成本之间作出取舍,定义领域内什么叫对 |
| Distinguished | 公司乃至行业 | 建立能够影响多个技术领域的标准和方法论,改变组织解决问题的方式 |
Junior 拿到任务,首先要回答怎么实现。Senior 还要回答需求有没有漏、怎样才算做完、上线以后出了问题怎么办。Staff 面对的情况更复杂:各个团队可能都完成了自己的任务,拼起来却没有解决共同的问题,需要有人重新定义接口、指标和责任。
再往上,连目标之间都会发生冲突。一个团队希望尽快退役 AB 实验,一个团队需要兼容老客户端,另一个团队要求保留故障回退。每一方都有理由。决定哪些承诺必须保留、哪部分可以延后、谁承担剩余风险,已经超出“这段代码怎么写”的范围。
职级的本质,是在多大范围上定义“什么叫对”,并把这份判断落实到结果。 写标准只是其中一部分,还要让标准能执行,让冲突有人处理,让结果不好时有人回来修正决定。
Agent 把这份责任提前了
传统团队里,大量实现工作由 Junior 和 Senior 承担,Senior 同时开始对模块的标准与结果负责。Agent 接过越来越多的实现之后,原本和写代码绑在一起的判断责任,就更早落到工程师面前。
如果沿用上面的职责坐标看这次变化,可以把它理解为整条阶梯的起点向上移动了一级:过去 Junior 主要在别人给定的标准下执行,新的入门要求已经包含为 Agent 定义任务边界、成功标准和验收方法。
| Agentic 时代的职级 | 核心职责 | 对应过去的职责重心 |
|---|---|---|
| Junior | 为一个边界明确的功能或模块定义成功标准,交给 Agent 实现,并取得完成证据 | Senior 的模块交付责任 |
| Senior | 为跨团队问题定义共同指标、接口契约和反馈机制,组织多个 Agent 与人的交付 | Staff 的跨团队责任 |
| Staff | 为一个技术领域或业务线定义正确性、演进方向和取舍原则,让局部自动化服从整体目标 | Principal 的领域责任 |
| Principal | 为公司乃至行业建立可以复用的标准和方法论,决定哪些能力共建、哪些判断必须留在具体业务里 | Distinguished 的组织与行业责任 |
| Distinguished | 为人和 Agent 的协作方式本身定义标准,包括责任分配、自治边界和工程人才的培养方式 | 新扩展出来的责任范围 |
这张表表达的是我对职责门槛变化的判断。它不会自动给任何人升职:Agent 帮一个人写出了过去 Senior 才能写出的代码,并不意味着这个人已经能承担 Senior 的责任。真正需要补齐的,正是中间那段业务理解、系统知识和验收能力。
对已经在路上的工程师,过去的经验因此有了新的用法。你见过兼容性事故,知道一个默认值为什么不能随便改,知道哪种依赖迟早会拖累系统。这些判断可以变成 Agent 的任务边界、验收用例和停止条件,不必每次都靠自己亲手实现来体现价值。
对刚入行的人,门槛则变得更直接了。只教写代码已经不够,还要从小范围任务开始教怎么定义完成、怎么找证据、怎么解释失败。团队不能一面把练习机会交给 Agent,一面指望新人凭空长出判断力。
沿着这条新阶梯往上走,下一步该学什么,就要看自己接下来准备承担哪一层责任。熟悉更多 Workflow 产品,不会自动扩大这个范围;能把原本只有自己看得懂的问题,变成团队可以共同判断和推进的工作,才算往前走了一步。
那些年攒下的经验又用上了
知道职责在变化,和知道自己能做什么,中间还差一步。让我慢慢踏实下来的是,很多新职责所需要的判断,恰好来自过去写代码、查问题时攒下的经验。
拿 AB 实验清理推演一下。下面是验收设计的例子,不是那个周末的完整项目记录。假设一个实验决定退役,最终保留实验组行为,Agent 很快删掉旧分支,编译通过,测试全绿。熟悉系统的人,这时还会继续追问:实验 owner 确认过退役了吗?老客户端还依赖对照组吗?这个开关是不是还承担紧急回退?共享 helper 有没有被别的实验使用?
做过兼容性改造的人,会想到老版本;处理过线上故障的人,会追问恢复路径;维护过实验平台的人,知道流量全部进入实验组,也不等于允许删除开关。过去这些经验帮助自己把代码写稳,现在可以用来定义 Agent 必须满足的条件。
| 已有经验提醒了什么 | 怎样转成可执行的判断 |
|---|---|
| 调用可能藏在跨模块封装里 | 列出分析产物和入口;每个目标引用有处置记录,未解析路径不能算完成 |
| 返回值没变也可能改坏行为 | 固定实验分组回放样本,同时比对输出、状态写入和外部调用 |
| 公共 helper 会影响其他实验 | 保留无关实验的独立回归样本,检查共享调用方 |
| 删掉代码后未必能靠改配置恢复 | 在约定环境演练回退,确认旧产物和配置仍然兼容 |
这就是定义指标、验收标准和验收方法的过程。指标决定看什么,标准决定接受什么,方法决定实际取得了什么证据。“残留引用数为 0”很容易写出来,知道少扫一个 module 就会让这个 0 失去意义,才用得上工程经验。
我在《Ground Truth:AI 时代最被低估的竞争力》和《Graphite: Code 即上下文》里强调过确定性工具的价值。但可复现不等于完备,工具的结论要放在输入和建模范围里理解。SootUp 的调用图文档区分分析范围和建图算法,核心概念文档也说明动态分派、反射会增加分析难度。
Agent 可以替我写出这样的工具,可要知道少给一个 JAR、漏掉一个入口会怎样,仍然需要程序分析知识。不懂这些知识,连应该拿什么程序验证工具都不知道。那些年学过的东西,在这里又用上了,只是交付物里很难直接看见。
在 agentic 项目里,这种经验还有一个很直接的用处:写各种 lint rule。上周,我刚从 codebase 里删掉了一万行代码。生成代码太廉价了,重复实现和已经没有用途的代码到处都是。写出来只要一会儿,留下来却都要有人理解、修改和维护。这一万行让我对“产出”有了更具体的感受:代码增加得快,和项目往前走得快,不能画等号。
清理一次之后,后续改动仍然可能把同类问题带回来。所以我会把能明确判定的问题写成 lint rule,在后续改动时运行这些检查。重复代码怎样识别、什么代码确实已经无用,都需要结合项目来判断;能沉淀成规则的部分,才交给工具反复执行。我喜欢把这些检查叫作 Agent 的“编译器”:把过去 review 时才说出口的判断,尽量变成它动手时就能收到的反馈。这样,我不必一直追在生成速度后面清理。
当然,这个“编译器”只能检查已经说清楚的规则。NASA 的系统工程手册区分了符合要求的 Verification 和满足实际用途的 Validation。就算每条规则都通过,如果实验退役的前提错了,仍然不该交付。工程师还得回来改标准,不能一直让 Agent 对着错误的标准修代码。
经验的价值,开始体现在我能定义什么、验证什么,以及知道什么还没有被证明。 这也解释了为什么单纯把规则写得更多没有用:规则可能过时,方法可能有盲区,判断要能够被反例推翻。只会说“我们一直这么做”,还不足以承担这份工作。
年初我盯着的是那一个小时,把它和自己的近二十年放在一起比。后来慢慢发现,近二十年里积累下来的,除了把代码写出来的手艺,还有对这些问题的理解。它们没有和实现成本一起消失,只是需要换一种方式用出来。
现在再问接下来该学什么
现在身边的工程师再说“不知道该学什么”,我能理解那种感觉。熟悉的路突然变了,眼前每天又冒出新的模型、框架和工具,很容易觉得不追就会掉队。但这三个季度下来,我给自己安排学习的顺序,越来越依赖手头准备承担的责任。
先为一个任务定义完成
准备承担新 Junior 职责,可以先选一个边界明确的任务,写清输入、预期结果、不能破坏的约束,以及缺什么信息就必须停止。交给 Agent 实现之后,自己要能解释为什么这次可以交付,哪些情况还没有覆盖。
这里缺什么,就补什么。说不清实验最终保留哪组,就去找 owner 和业务依据;不知道包装后的调用能不能分析出来,就读程序分析的文档,构造小程序验证;不知道回退是否有效,就去理解发布和配置的关系。领域知识、系统基础和验证方法,要在同一个任务里接起来。
测试里的答案也需要来源。Test oracle problem讨论的就是如何判断输出正确。我在《Agent 真的需要 TDD 吗?》里也写过:实现和测试如果共享同一份误解,全部通过也可能只是自证。能找到独立于实现的业务样本、协议要求或事故记录,是这一步需要练的能力。
对新人,先预测,再看 Agent 的实现,再跑反例、解释差异,由有经验的人核对判断依据。编程仍然是理解系统、检验想法的重要手段。可以让 Agent 减少手工输入,但不能把理解系统的过程一起跳过。
再把一类判断交给系统
已经能独立负责模块的工程师,可以从自己反复做的 review 入手。每次觉得“不对劲”,先留下原因和最小反例,再去修代码。同一类失败反复出现,就值得把检查沉淀下来,看看下一次不同的改动是否也能被正确判断。
这一步需要学习怎样验证检查本身。Mutation Testing可以检查测试是否会对某些错误作出反应,但它不能证明需求完整。保留独立评估样本也有用,不过,Dwork 等人关于 holdout 重用的研究提醒过,反复依据同一批保留样本调优,会带来过拟合风险。
因此,积累不能只看测试数量和通过率。还要检查答案的来源、采集范围是否被缩小、失败样本有没有被删掉、报告对应哪一版产物,以及旧标准是否仍然适用。遇到证据缺失,检查应该返回“未知”,而不是默认通过。
别人也能根据这套反馈定位和修复问题,你就不用每次都亲自看完。让一类任务都能这样推进,也是开始承担跨团队责任的一步。
把标准带到更大的范围
准备承担新 Senior 职责,就要把这套方法带到团队之间。有人追求退役速度,有人担心兼容性,有人承担恢复责任,需要共同确定指标、边界、先后顺序和例外。把一个模块的检查做得很严,还不足以解决这些冲突。
到了 Staff,注意力要扩展到领域内的长期取舍:哪些接口和数据模型应该统一,哪些兼容性承诺不能破,局部交付速度是否正在增加整体维护成本。Principal 还要把有效的方法推广到不同领域,区分哪些标准值得统一、哪些差异必须保留。
Distinguished 则要继续追问协作方式本身:人在什么证据下可以放手,Agent 的自治边界怎样调整,新一代工程师通过什么工作积累经验。工具能力在变,组织怎样分配责任、培养人,也需要有人持续修正。
这些责任靠多读几篇文章拿不到。需要参与相应范围的问题,记录当时的依据、作出的选择和后来的结果,再用结果校准判断。判断自己有没有进步,可以问:这一次,我是否比上一次多弄清楚了一层约束,多承担了一段过去要交给别人决定的事情?
每一步也都要算成本。一次性问题,人工看几分钟就能确认,不值得先造一个庞大的 Harness。值得沉淀的是反复出现、后果重要、反馈可以稳定取得的判断。能决定哪些事情暂时不做,同样是独立负责工作的一部分。
时代又一次把我们带到了这里
走过这三个季度,再看年初那个问题,我慢慢有了答案。实现可以交给 Agent,理解问题、定义标准、验证结果的工作,还需要继续往深处做。近二十年的经验有了新的用法,我也知道接下来该把精力放在哪里。
想到这里,我开始觉得,眼前不只是职业上的一道坎,也是一份很难得的机会。AI 把一批原来做不起、做不完的事情,放回了可以尝试的范围。那个一直搁着的字节码工具,对我来说就是一个很小、却很具体的例子。年初我只顾着拿那一个小时和自己的手艺比较,现在想的是,以后还有多少这样的念头,不必再一直搁着。
一个人的职业生涯里,能赶上一次这样的变化已经不容易。作为 80 后,我们先赶上了移动互联网,如今又站在 AI 这波浪潮里。还有精力,也已经有些积累的时候,再遇到一次改变做事方式的机会,对我们这一代工程师来说,称得上千载难逢。
想起上一波浪潮,还是在滴滴做 VirtualAPK 和 Booster 的那几年。我在《在滴滴工作的那些年》里记过这些项目的经历。移动互联网带来新的业务,也带来新的工程问题,让我们有机会把想法做成项目,再通过开源被更多人用到。回头看,个人的成长和时代给的机会,很难分得开。
当年做 Booster 时,还有一些想法没来得及实现,那个字节码工具就是其中之一。如今继续做 Graphite,过去的问题和积累,又在 AI 时代找到了新的用处。两波浪潮之间,自己的经历也就这样接了起来。
这一次,浪潮又把我们带到了新的地方。我不知道接下来会出现什么样的产品,会和谁一起做成什么事,也给不出一份能保住饭碗的技术清单。Agent 还会继续进步,今天需要自己做的事,明天也许就有现成产品接过去。
但我想继续做下去。带着这近二十年攒下的经验,把那些曾经来不及做、不敢做的事情,再拿出来试一试。
我们还在场。这一次,也还有机会做出一些值得留下来的东西。
- 本文链接:https://johnsonlee.io/2026/10/09/at-the-crest-of-another-wave/
- 版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
