最近半年,我们在代码库中看到越来越多这样的提交:一个功能完整的 PR,包含数千行代码,单测覆盖率达到 85% 以上,本地集成测试全绿。然而在提测演示或做方案复盘时,提交代码的工程师无法在不借助模型的情况下,准确描述出某条核心业务链路在异常分支下的具体状态扭转过程。
代码由大模型直接生成,工程师负责提供提示词、粘贴报错、运行命令以及验收功能。这种被称为 vibe coding 的开发模式正在研发团队中快速蔓延。开发人员正在迅速丧失对代码内部实现细节的掌握。
如果一个开发人员完全不需要知道代码内部发生了什么,只要在外部通过输入输出来验证系统,这项工作换成一个不懂技术的产品经理或业务人员,结果并没有本质区别。 当编写代码的动作被模型接管之后,技术人员本身的专业壁垒就会受到直接冲击。
在实际工程推进中,不懂底层原理的使用者在与模型交互时,会迅速遇到一道无法逾越的墙。当系统行为偏离预期,或者出现跨多个子系统的复合型偶发故障时,缺乏系统内部认知的人甚至无法组织出有效的排查提示词。他们只能把控制台的错误堆栈反复扔给模型,陷入尝试、失败、再尝试的低效死循环。
当然,这种情况在模型和 Harness 越来越强的情况下,越来越少了。
当前,判断开发工程师价值的关键指标,在于具体业务子领域是否还需要我们深入理解系统的内部实现,以及需要理解到何种程度。
复杂系统的黑盒沉降
我们原本设想黑盒开发只会停留在低风险的边缘地带。那些生命周期以周计的营销脚本、孤立的数据管道、或者一次性的内部报表工具,被开发者直接当成黑盒丢给模型演化。只要输入和输出符合预期,内部堆叠了多少冗余逻辑,人类完全不需要过问。
这种边界在过去半年里被全面打破。
在真实的研发场景中,复杂的长周期核心系统同样正在被迅速黑盒化。系统的实现逻辑已经开始脱离了我们的掌控。
有些系统已经没有任何一个工程师完整读过底层实现,所有人都在依靠行为验收来驱动迭代。
这一变化的驱动力来自工程吞吐量的极端失衡。过去由一个六人资深团队耗时三个月才能搭建完的复杂状态机,现在借助高阶推理模型,两天内就能生成数万行代码,并配套数千个通过的单元测试与端到端用例。当代码生成的速率超越人类视网膜与大脑工作记忆的物理极限时,人工逐行审查机制就失去了事实上的防御能力。
我们开始被迫接受这种现实。 在面对极其庞大的调用拓扑和复杂的上下文依赖时,我们已经放弃了对抽象语法树的微观控制,转而把整个复杂系统视作一个自组织的、不透明的动力学网络。
这种转变直接剥夺了传统工程学给我们带来的安全感。当不懂内部机理的团队开始掌控这种复杂黑盒系统时,表面上的研发效率提升与潜伏的系统性崩溃风险就绑定在了一起。
演化哲学的工程代价
把软件代码视同生物 DNA 序列,认为系统可以在目标函数的约束下自然演化,是黑盒派的核心立论。生物演化积累了大量的无用突变、历史包袱与内含子序列,依然能在残酷的自然选择中维持机体运转。在很多开发者的设想里,只要外部的行为约束足够严格,内部的代码哪怕堆积成山,系统也能通过模型的自我修剪持续向后演化。
在复杂系统中使用演化哲学,会遭遇物理层面的硬约束。
软件系统与生物体存在本质区别。生物体拥有物理世界施加的绝对法则限制,而软件的目标函数是由人类通过测试套件和契约规范手工定义的。人类工程师编写的测试用例,无论规模多么庞大,都只能覆盖有限的状态空间。
当模型在复杂的业务系统里自我演化时,它会不断寻找使测试用例全绿的阻力最小路径。在处理一个涉及分布式两阶段提交的业务分支时,模型为了修复一个并发竞争的偶发报错,可能会在关键路径上引入一段极其隐蔽的全局读写锁,或者擅自放宽事务隔离级别。从行为验收的角度看,那十几个报错的测试用例确实顺利通过了。
这种微小的局部优化在多轮提示词和版本迭代后,会引发全局架构的雪崩。并发瓶颈从数据库行锁被硬生生搬运到了应用层内存,系统的吞吐量在特定流量脉冲下出现断崖式下跌。由于团队没有人理解这段被模型演化出来的内部调度机制,监控指标只能呈现出 CPU 软中断升高与连接池耗尽,排查人员根本无法把这种宏观症状与某个被模型悄悄修改的同步原语对应起来。
生物演化历经数十亿年,其代价是无数个体的死亡与物种灭绝。在商业软件工程中,任何一次演化走入死胡同,换来的都是真实的资损、核心数据损坏与长达数小时的服务不可用。
重写的大坑与暗知识
当代码产出如此快速时,以前不敢做的重构操作,现在频频出现在现实之中。
并且对于黑盒的代码,当无法维护时,会有人开始直接让 AI 来重写了。
对于极度依赖隐性规则的复杂系统,重写会是一个大坑。
复杂系统的内部实现中,沉淀了海量的暗知识。这些暗知识由线上历史事故、冷门协议的缺陷规避、上下游陈旧系统的怪异行为,以及特定硬件环境下的性能妥协交织而成。在漫长的迭代周期里,这些细节几乎不可能被完整记录在需求文档或提示词工程的上下文中。
当一个承载核心业务的复杂黑盒系统遭遇架构死锁时,工程师试图命令模型从零重写一套全新的系统。模型根据现有的规范文档和接口契约,迅速生成了一个结构干净、抽象完美的新架构。但在接入真实生产流量的瞬间,系统会被各种意想不到的边缘异常彻底击穿。
老系统中那些看似丑陋的防重试逻辑、硬编码的延时等待以及反常的类型转换,恰恰是系统在生产环境存活多年的抗体。在黑盒演化过程中,这些抗体没有被人类工程师转化为显性的设计原则,而是散落在无法辨识的代码废墟中。
当模型完全接管了代码,人类不再阅读实现细节,这些暗知识就随着上下文窗口的更迭永久失传了。重写一个复杂黑盒系统,意味着要重新把过去五年踩过的所有生产故障从头经历一遍。这种重写代价根本不是代码生成速度能够弥补的。
工程师的剩余价值
在复杂的长周期系统也逐步被黑盒代码吞噬的周期里,技术团队内充斥着工程师技能贬值的焦虑。天天面对自己看不懂或者懒得去看的自动化生成代码,开发人员的专业尊严受到了直接侵蚀。
开发人员的剩余价值非但没有消失,反而在系统复杂度失控的悬崖边缘被急速放大。只不过,这种价值的落点发生转移。
编写算法、拼装框架、修补语法的低阶体力劳动被彻底剥离。工程师的核心价值,收敛为以下几项不可替代的底层能力:
第一,是定义系统物理边界与失败容忍度的系统论设计能力。当实现细节彻底黑盒化,整个系统的生存完全取决于外部边界是否足够坚固。如何设计熔断逻辑,如何划分数据强一致性与最终一致性的边界,如何定义灾难恢复时的降级降速策略,这些决策直接关乎企业的商业生死,不可能托付给缺乏全局上下文责任感的模型。
第二,是穿透软件抽象层、理解物理基础设施底层的能力。不论上层代码被模型演化得多像一个不可名状的黑盒,当它最终被编译成汇编指令落到物理 CPU、内存条、网卡与固态硬盘上时,它依然必须服从物理世界的客观定律。
在跨机房专线延迟突增、底层存储硬件出现坏块、操作系统内核调度产生微秒级锁死等极端场景下,缺乏真实物理世界感知的黑盒系统会瞬间丧失全部应对能力。此时能够挽救全局的,永远是那些清楚 Linux 内核参数配置、深刻理解 TCP 拥塞控制算法、明白数据库 B+ 树与 LSM-Tree 存储引擎底层权衡的工程师。
第三,是决定何处必须保留白盒的战略决断力。在复杂的业务全景图中,我们必须划出一条绝对的红线。在这条红线之外,允许模型疯狂试错、野蛮生长、黑盒演化,以换取交付速度;而在红线之内,在涉及资产结算、核心密码学协议、权限鉴权核心以及数据持久化原子性的基石模块上,我们必须死守白盒阵地,每一个状态位、每一行锁逻辑、每一个并发屏障,都必须由人类大脑完全理解、推演与严格审计。
拥抱黑盒演化是应对代码生产力爆发的必然妥协,但盲目迷信黑盒则是工程理性的自杀。
白盒边界的落地
不过,把红线画出来,还远远不够。
如果白盒意味着所有代码都要由人逐行理解,那么几万行的关键模块很快就会耗尽团队的审查能力。如果白盒只意味着安排一位资深工程师签字,它又会退化成一种形式上的责任分配:代码已经合入,签字的人却无法解释失败路径。
从实际出发,白盒要求可以落实到几个可以检查的问题上:
这个模块维护哪些状态?哪些状态转换绝对不允许发生?一次操作在什么位置产生不可撤销的影响?执行中断以后,如何判断它究竟完成到了哪里?修改这里,会影响哪些调用方的既有假设?
负责的工程师需要能够独立回答这些问题,并且指出对应的实现位置。
这里仍然允许模型生成代码。我们限制的是未经理解的关键变更进入系统。生成与理解可以由不同的过程完成,但理解不能被一份自动生成的说明替代。
边界还需要沿着依赖关系检查。
假设一个关键模块本身经过了严格审查,但它依赖的公共组件被模型修改了失败处理方式,原有结论就可能失效。白盒边界如果只按目录划分,很容易漏掉这种变化。把关键模块依赖的行为约定一起纳入审查范围,尤其是超时、重试、错误返回和状态写入的语义。
这样做会拖慢部分公共组件的修改速度。我们需要接受这笔成本,也需要控制它的规模。如果一个普通改动总要召集半个团队评审,说明关键模块依赖了过多外部细节,应该考虑收缩接口和状态共享范围。
白盒区域也不必永久固定。一个模块的状态被拆出、接口变得稳定、替换方式得到验证之后,可以降低内部审查强度。一个原本独立的工具开始承接共享数据写入,就应该重新评估。
「核心业务」四个字无法覆盖整个代码库。范围画得太大,最后往往只能整体降低执行标准。
验证独立
黑盒能接受到什么程度,取决于我们能够从外部验证什么。
这里最容易出现的问题,是实现、测试和验收结论都来自同一条生成链路。模型先理解需求,再生成代码,随后根据代码补齐测试,最后告诉工程师所有测试已经通过。
如果最初的理解遗漏了一个条件,这个条件就可能同时从实现与测试中消失。
覆盖率无法自动发现这种遗漏。某一行代码被执行过,只能证明测试经过了那里。异常分支执行以后留下的状态是否正确,仍然需要另外检查。
因此,关键验收条件在实现之前确定。至少把正常结果、禁止出现的结果,以及失败后允许留下的状态写清楚。
尤其要检查那些跨越多个动作的过程:前一个动作已经完成,后一个动作失败,系统应该停在哪里?用户重新发起请求时,允许重新执行哪些部分?超时之后,调用方能否把结果当成失败?
这些问题没有答案时,继续增加测试数量并不能补上设计缺口。
模型可以帮助枚举场景、生成数据、执行验证。涉及业务承诺的条件,需要由了解系统的人确认。验收阶段还应保留独立于当前实现的依据,避免模型通过修改预期结果,让实现重新获得通过。
测试本身的变更也要审查。
删除一个失败用例,有时是因为旧需求确实失效,有时却只是当前实现暂时无法满足它。二者在测试报告上没有区别。对稳定约束的删除、放宽和跳过,我会要求单独解释,不能夹在几千行功能改动里一起合入。
验证强度当然有成本。复杂故障场景需要准备环境,大规模数据验证消耗资源,长时间运行的检查会增加反馈延迟。我的做法是分开执行频率:局部检查跟随每次修改,影响面较大的验证放在合入和发布环节,昂贵的故障验证集中覆盖关键路径。
目标是让不同风险都有对应的检查位置,同时避免每次修改都等待一套庞大的验证流程。
限制单次变化
代码生成速度上来以后,团队很容易把更大的任务直接交给 agent。
一个需求里同时包含接口调整、状态机修改、数据结构迁移和历史代码整理。模型可能一次完成,最终差异也显得相当统一。但人工审查时,很难再把每一处变化与最初的动机对应起来。
发生回归以后,定位范围同样会扩大。
我们要控制单次变更所包含的独立决策数量。功能增加、结构整理和历史兼容性清理,尽量分开进行。每一部分都需要有能够单独解释的目的,以及对应的验证结果。
这里不适合机械地规定 PR 行数。一次自动生成的重复性修改可能涉及很多文件,实际语义变化很少;一个几行的条件调整,也可能改变整个系统的状态约束。
这次只改变了哪些行为,哪些证据支持这些变化,以及如何撤销?
让 agent 先提交修改计划,也有帮助。人可以在生成大量代码之前检查它准备触及哪些模块,是否引入新的共享状态,是否扩大已有接口的职责。
当然,计划不能被当成执行事实。模型在修复过程中可能偏离原计划,最终仍然需要核对实际改动。尤其要检查那些为了让测试通过而新增的兼容分支、默认值和异常处理。
另外,权限应该跟着风险划分。修改实现、调整验收标准、操作生产数据,是三个不同等级的动作。不能因为 agent 已经获得代码仓库的写权限,就顺带允许它自行决定另外两件事。
保留接管能力
即便验证做得足够认真,团队仍然需要准备一种情况:系统出了问题,agent 连续几轮都没有找到原因。
这时工程师需要接管什么?
首先要接管的是故障判断和损失控制。当前还有哪些请求正在写入?哪些动作可以暂停?哪些状态已经无法直接撤销?继续重试是否会重复产生副作用?
如果这些问题无法回答,贸然修复代码可能让后续处理更困难。
因此,关键路径上的可观测信息需要围绕状态变化设计。只记录异常堆栈,通常不足以判断一个业务动作完成到了哪里。需要能够把一次操作的开始、关键写入和最终结果关联起来,同时区分「没有执行」与「执行完成但没有返回」。
信息越多,记录与检索的成本也越高。涉及数据内容时,还需要限制记录范围。我不会要求把所有局部变量都写进日志,而会优先保留能够判断执行阶段和副作用的信息。
恢复方案也要区分代码与数据。
代码版本退回以后,新版本已经写入的数据仍然存在。旧实现能否读取这些数据,正在执行的任务能否继续,外部已经发生的动作如何处理,都需要提前确认。一个发布平台上存在的回滚按钮,不能证明业务状态能够恢复。
团队可以让模型生成恢复方案,但需要通过演练检查其中的假设。演练时暴露出一个无法判断完成状态的步骤,就应该补充相应的记录或操作方式。
这些工作会占用原本可以拿来交付功能的时间。我们要优先安排给那些失败后难以恢复、影响范围又大的模块。对于可以直接丢弃并重新生成的结果,没有必要采用同样的投入标准。
我们也不应该假设所有故障最终都需要人手工解决。agent 能完成的排查可以继续交给它。人需要保留的是判断它是否仍在有效推进的能力,以及必要时改变排查方向、停止危险操作的权限。
把暗知识留下
关于暗知识,还有一个地方需要说得更准确。
只要代码、历史记录和运行证据仍然存在,很多知识就有机会被重新找回来。但随着版本累积,重新发现它们需要的时间会增加。故障发生时,团队未必有这个时间。
在每次关键修改中,顺手留下三类信息:这段特殊处理保护了什么约束,当初在什么条件下出现过问题,以及什么证据能够证明它将来可以被删除。
单独写一句「兼容历史逻辑」,对后来的维护者帮助很有限。模型也无法据此判断,这段逻辑究竟承担了必要的保护,还是早已失效的补丁。
记录需要贴近决策发生的位置。约束可以进入验收条件,历史问题可以转化为回归场景,难以自动验证的条件则保留在模块说明和变更记录里。没有必要把所有知识都塞进一份不断膨胀的架构文档。
尤其要防止另一种浪费:模型生成了大量说明,团队却没有检查其中哪些是事实,哪些只是根据代码推测出来的意图。
「当前代码这样执行」和「系统必须这样执行」需要分开记录。前者描述实现,后者约束未来修改。混在一起,模型可能把历史偶然行为永久固化,也可能把必须保留的规则当成可整理的细节删除。
重写之前,这些记录应当成为核对清单。找不到来源的特殊逻辑,需要进一步追踪,不能仅凭实现难看就判定它没有价值。
同样,也不能把所有历史补丁都当作不可触碰的要求。长期保留失效约束,会让新系统继续背负旧系统的复杂度。删除它们需要证据,保留它们也应该能够说明理由。
重新分配时间
这会改变工程师日常工作的时间分配。
一部分原本用于编写常规实现的时间,可以转移到约束定义、关键路径审查、失败恢复和演化设计上。另一部分确实可以节省下来,用于交付更多需求。我们没有必要为了证明专业价值,把节省的时间全部重新填满。
不要求所有工程师都深入内核、网络协议和存储引擎。基础设施知识在某些问题上非常关键,但大量故障仍然来自业务状态、依赖关系和错误的边界假设。团队需要根据系统的风险分布建立能力,不能用一套底层知识清单替代具体判断。
对于个人:能否识别模型没有回答的问题,能否发现验证结论依赖了未经确认的假设,能否在陌生实现中定位关键状态,以及能否解释一次技术选择会给后续修改增加什么成本。
这些能力也需要通过实际接触代码培养。如果年轻工程师长期只负责转发需求和粘贴错误,却没有机会追踪实现、分析失败、参与关键决策,团队几年后可能会出现知识传承的问题。
因此,我们需要让工程师对完整的局部问题负责:提出约束、使用模型实现、解释关键行为、验证异常路径,并参与上线后的问题处理。模型可以承担其中大量工作,但负责的人需要能够检查结果。
团队层面的评价也要变化。生成了多少代码、完成了多少轮对话,都不适合作为主要指标。我们需要观察一次需求从提出到稳定运行花了多久,评审与返工占用了多少时间,线上问题是否反复出现,以及一个模块是否越来越难由其他人接手。
如果编码时间缩短了,排查和返工时间却持续增长,就需要调整当前流程。交付数量暂时增加,也不能掩盖恢复能力和知识覆盖的下降。
回到最初那个数千行代码、测试全绿的 PR,我们不会因为提交人无法背出全部实现就否定它。
但如果他无法解释核心异常分支的状态变化,我们要要求补上这部分理解:追踪关键写入,核对失败后的状态,检查重复执行的后果,再决定是否需要增加验证或修改实现。
这个 PR 可以继续由模型完成修改。合入之前,负责的工程师需要知道哪些结论已经得到验证,哪些风险仍然存在,以及出问题以后应该从哪里开始处理。
以上。