假设一个产品系列使用了多种处理器、不同容量的内存和几套外围硬件。它们共享大部分代码,但工具链、配置选项和最终制品并不完全相同。现在要修改其中一个公共模块,AI 很快就能理解接口、生成代码并补上测试,过去几天的工作可能缩短到几十分钟。
但对构建团队来说,工作才刚刚开始:这次修改影响哪些产品?旧型号还能不能编译?不同工具链产生的制品是否仍然满足相同的验收要求?新增的依赖能不能用于所有硬件平台?本地构建成功,是因为声明完整,还是因为开发机器上刚好安装了某个软件包?
AI 降低了得到一份实现的成本,却没有自动回答这些问题。实现越容易产生,工程体系越需要有能力判断:哪些变更可以进入系统,会引起哪些构建和重建,又该怎样验证。构建之外的功能、安全和运行影响,还需要其他工程环节共同判断。
实现可以重写,系统关系不能一键重来
AI 降低的不只是编写第一份代码的成本,也降低了修改、推翻和重新尝试的成本。让它换一种实现、去掉一个依赖或者保留旧接口,通常只需要补充几条要求。尚未进入系统的代码,越来越像一种用于探索问题的中间产物,而不再天然代表已经形成的工程承诺。
但是,这种低成本只存在于变更还没有深入系统的时候。
一项实现一旦进入系统,就会逐渐与其他组件、接口、数据、工具链、运行环境和交付要求建立关系。这些约束并不按行业整齐分开:通信设备既有 CPU 架构、内存规格、厂商 SDK 和产品型号这样的显性边界,也要持续演进 API、协议和运行环境;大型互联网系统虽然通常没有同样固定的硬件产品组合,却同样受基础镜像、语言运行时、数据库结构、公开 API 和消息协议等约束,其中不少一旦被大量服务依赖,也会成为不能随意突破的边界。
因此,AI 可以很快重写一个公共模块,也可以很快修改一个服务,但成本已经不再只来自实现本身。一个底层组件的变化可能牵动几十种硬件组合,也可能沿着公共库、接口和服务依赖传播到大量软件系统。行业不同,约束的形状不同,变化的节奏和验证方式也不同;真正共同的问题,是如何在这些既有关系中吸收变化。代码可以很快重写,已经形成的系统关系却不能一键清除。数据库格式、公开接口、硬件协议、编译工具链、测试资产和产品维护承诺,都会让后续修改承担相应的兼容、重建和验证成本。
这里还有一个容易忽略的不对称:重新生成实现的成本下降了,理解实现的成本未必下降。AI 可以很快重写一个模块,但评审者仍然需要理解它改变了什么隐含行为,为什么引入新的抽象,是否增加了故障模式,以及其他产品配置会不会受到影响。
生成速度越快,理解、验证和集成在总成本中的占比反而可能越高。
为什么验证不会自然地以同样速度变便宜
既然 AI 可以生成代码,当然也可以用 AI 评审代码、编写测试和分析影响。那么,生成和验证是不是可以一起变便宜?
AI 确实能降低一部分分析和验证成本,但完成一项实现与正式接受一项变更并不是同一件事。对 AI 而言,完成实现通常意味着找到一条能够满足当前要求的路径;对工程体系而言,接受这项变更,还需要针对重要的失效方式取得足够证据。
例如,一个嵌入式驱动在默认开发板上编译通过,测试也没有报错,并不能说明它已经适用于整个产品系列。某些型号可能使用不同的编译器版本,某些型号的内存空间更紧张,还有一些配置可能启用了不同的中断、并发或者低功耗路径。这些差异平时不一定出现在 AI 获得的局部代码上下文中,却可能决定最终制品能不能使用。
这并不意味着所有产品组合都必须在每次修改后完整验证。工程仍然需要根据影响范围和失败后果选择验证强度。影响有限、容易发现并且可以恢复的变更,可以采用较轻的验证方式;涉及公共底层模块、硬件接口或者安全边界的变更,则需要更充分的证据。
AI 可以参与实现,也可以参与验证,但不能因为同一个 AI 智能体(Agent)同时生成了代码和测试,就认为这些测试已经提供了独立证据。它可能在两个阶段都沿用了同一个错误假设。
所以,AI 能够降低验证成本,却没有消除验证责任。实现的生成速度和工程体系接受变更的速度,仍然受不同条件约束。
真正的瓶颈是怎样吸收变更
AI 不一定会让所有行业更快地发布软件。在车规、通信设备、工业控制和大型遗留系统中,产品准入和验证流程仍然可能保持原来的节奏。真正发生变化的,是更多实现和修改能够以更低成本到达工程体系的入口。
如果一个大规模软件项目同时使用多个编码 Agent,它们可以很快修改不同模块。单独看,每项修改可能都很合理;组合起来,却可能增加依赖数量、扩大工具链版本范围、引入重复抽象,或者让构建和测试矩阵不断膨胀。代码生成能力越强,验证队列反而越容易成为瓶颈。
因此,工程体系需要的不只是更快的流水线,还需要处理变更压力的能力。这里可以把它称为“变更吸收能力”。它不是把更多修改吞进系统的速度,而是把一项变更转化为接受、拒绝、推迟、缩小范围或重新设计等明确的处置决定,并为这些决定提供与风险相称的证据。
例如,AI 为一个公共模块引入了新的软件包,但这个软件包只支持部分产品使用的工具链。合理的处理方式未必是让构建团队想办法把所有平台都适配过去,也可能是要求重新设计,把新依赖限制在真正需要它的范围内。
能及时阻止一项不合适的变更,同样是工程能力。否则,所谓提高交付速度,只是把今天节省的时间变成未来需要偿还的复杂性。
“变更吸收能力”也不应该成为新的产能指标。生成了多少代码、创建了多少合并请求、执行了多少次构建,都不能单独说明工程效率提高了。真正重要的是:系统能否在证据不被稀释、责任不被模糊、复杂性不持续失控的情况下处理这些变更。
工程的平衡从边界开始
要判断一项变更应该接受、调整还是拒绝,前提是知道哪些条件可以权衡,哪些边界不能突破。
技术选择很少在所有方向上都更好。提高性能可能增加复杂性,扩大兼容范围会提高验证成本,更严格的隔离也可能降低开发便利性。工程不是追求所有指标同时达到最好,而是在现实约束中寻找合适的平衡。
但平衡不等于什么都可以交换。不可突破的边界一旦明确,权衡才发生在剩余空间里。
在嵌入式和车载系统中,产品侧的资源预算、实时性和安全要求,不能因交付压力而突破;构建侧已经确定的执行隔离、工具链范围和依赖约束,也不能仅仅为了缩短构建时间而被关闭。纯软件系统同样存在不能随意交换的边界,例如已经对外承诺的接口兼容性、数据格式、运行平台要求和合规约束。边界的具体形态会随产品而不同,但一旦成为明确的产品或工程约束,就不能再简单拿它们与开发进度交换。
在这些边界以内,仍然存在大量需要平衡的选择。例如,是每次修改都验证所有硬件规格,还是先根据影响范围选择必要的构建和测试集合?是继续支持一个使用量很低的旧工具链,还是把它冻结到维护分支?是扩大缓存提高速度,还是减少缓存共享来增强隔离?
这才是“工程是一门妥协的艺术”的准确含义。妥协不是为降低标准寻找理由,而是在不可突破的条件下,明确选择保护什么、放弃什么,并承担相应的代价。
风险接受权本身也有边界。项目不能因为进度紧张,就通过一次内部批准把法律、监管、合同或者安全要求重新定义成普通的技术选项。有权决定项目计划,不代表有权豁免外部义务。
构建工程要控制什么能够影响制品
当实现供应越来越快,只依靠工程师记忆产品差异、手工检查环境和逐项维护流水线,很难持续处理不断增长的变更压力。已经明确的工程要求,需要尽可能变成可以重复执行的系统能力。
本文所说的构建工程,不只是运行一次编译命令,而是围绕制品生产形成的构建与验证体系。它包括依赖关系、工具链、执行环境、测试、打包、制品来源和准入过程,关心的是一次代码变更怎样成为可以交付的制品,以及这个过程能否被解释和验证。完整的业务决策和生产运行治理,则不属于本文讨论的构建工程范围。
做好这件事,至少需要同时解决两个问题。
第一个问题是说清楚哪些因素允许影响制品。源码、第三方依赖、编译器、系统软件、构建参数、硬件规格和产品配置,都可能改变最终结果。对于拥有多个产品系列的团队来说,“同一份源码”远远不足以确定一个制品,目标硬件、功能选项和工具链同样是输入。
第二个问题是确保在明确的构建信任边界内,只有这些被声明和授权的输入能够参与构建。构建边界之外仍不可避免地依赖的平台、硬件和服务,都应被明确列为信任假设。依赖声明写得再完整,只要构建脚本仍然可以随意读取开发机器上的文件、调用未声明的工具或者访问外部网络,实际结果就仍然可能被环境中的偶然因素改变。
一个常见场景是:某位工程师的机器上已经安装了厂商 SDK,构建脚本通过默认搜索路径找到了它,所以本地一直能够成功。换到另一台机器或者 CI 环境后,构建突然失败。更麻烦的情况是,两边都能构建成功,却因为找到的 SDK 版本不同而产生了不同的制品。
这里需要限定一下:构建系统擅长计算的是构建和重建影响。例如,一项输入变化后,哪些组件以及哪些产品配置对应的制品需要重新构建;根据预先建立的验证规则,哪些相关验证需要重新执行。至于功能行为、安全风险和运行影响,还不能只靠构建图判断。
这类问题不能只靠提醒开发者“记得声明依赖”解决。执行环境必须真正限制构建能够读取的文件、工具和网络资源。如果构建需要未声明的输入,它应该直接失败,让缺失的依赖暴露出来,而不是悄悄使用当前机器上碰巧存在的内容。
可以把这两个条件理解为“说清楚”和“管得住”:前者明确哪些输入可以影响制品,后者阻止未声明的输入影响构建结果。只有声明而没有执行限制,工程系统仍然依赖自觉;只有隔离而没有清楚的声明,系统又很难解释为什么能够构建。
后续工程首先需要一个确定的制品
过去一个常见做法是,开发团队构建一份软件用于自测,测试团队拿到相同代码后重新构建,上线前又在发布环境中构建一次。表面上三次使用的是同一份代码,实际却经过了三套可能存在差异的工具链、配置和系统环境。开发验证过的、测试通过的和最后上线的,未必是同一个软件制品。
即使团队统一了研发环境,也很难保证环境长期完全一致。一次工具升级、一项配置调整,或者机器上多安装了一个软件包,都可能改变构建结果。源码没有变化,并不代表制品没有变化。
因此,构建工程首先要解决的是确定性:相同的输入应当得到相同的输出。更进一步,面向交付的正式制品应当尽量只构建一次,让同一个制品依次进入测试、安全检查、发布和部署,而不是在每个阶段重新构建一个“看起来相同”的版本。
这一步是后续工程实践的基础。测试团队需要知道自己验证的就是最终交付的制品;安全和合规检查需要绑定到确定的制品;发布系统需要准确记录它的来源;运维系统才能可靠地部署、回滚和追踪它。连制品本身都不能确定,后面的证据和控制就失去了稳定的对象。
构建工程因此处在一个承上启下的位置:它承接研发产生的源码、依赖和配置,把它们变成确定的制品;另一边则连接质量验证、供应链安全、产品准入、发布部署和运维管理。它不是这些控制面的替代者,却为它们提供了共同工作的基础。
不是所有要求都能变成构建规则
确定的制品为后续验证提供了稳定对象,但它并不意味着所有工程属性都能由构建系统自动证明。
构建工程能够把很多要求转化成机器可以执行的控制,例如依赖版本、工具链范围、访问权限、制品大小、接口兼容检查和必须通过的测试。但并不是所有工程边界都能由构建系统独立证明。
例如,一个通信设备在特定负载下是否能够长期稳定运行,可能需要真实硬件和长时间测试;一个控制系统在异常条件下能否正确降级,可能需要台架或者系统级验证;某些安全要求还依赖部署方式和运行流程。
成熟的工程体系不应该假装只要策略语言足够强,所有要求最终都能写进流水线。构建工程真正需要做的是,把能够机器化的部分转化成不能轻易绕过的控制,同时明确暴露哪些条件仍然依赖人工评估、专门测试或者运行环境中的证据。
“无法自动验证”本身也应该是一项可见的工程事实,而不应该被隐藏在某位资深工程师的经验里。
Agent 越聪明,能力边界越重要
Agent 让执行边界变得更加重要,而不是不再重要。
假设 Agent 在构建过程中发现缺少一个头文件。为了尽快完成任务,它可能从网络下载一个软件包,也可能调整搜索路径,使用宿主机上已经安装的版本。从 Agent 的角度看,它成功解决了构建问题;从工程角度看,它可能绕过了依赖声明,引入了一个无法稳定复现的环境条件。
Agent 还会读取仓库里的说明文档、问题记录和测试日志。这些内容既可能包含有用信息,也可能包含错误甚至恶意指令。即使最终代码看起来合理,也不能据此相信整个执行过程只使用了允许的输入。
因此,Agent 应当像其他高能力执行主体一样受到最小权限、输入隔离和行为审计。它能够解释自己做了什么,不代表这些解释可以代替系统证据;它表现得越智能,越不能只依赖它自觉遵守边界。
AI 能力扩大了自动化可以完成的工作范围,也放大了错误操作可能造成的影响。能力越强,权限和执行边界越需要由外部系统控制。
构建系统提供证据,但不替组织作出决定
即使依赖声明完整、执行环境封闭、所有构建和测试都通过,也不能由此证明一项工程选择本身一定明智。构建系统负责执行已经确定的验证要求,并记录哪些已经完成、哪些仍然缺失。至于什么样的证据才算足够,仍然需要相关工程团队根据产品用途和风险作出判断。
例如,升级一套公共工具链以后,构建系统可以计算哪些产品需要重新生成制品并执行相应验证,同时记录新旧结果。它还可以发现某些旧硬件无法通过验证。但是否停止维护旧型号,是否扩大验证范围,或者是否接受某项剩余风险,并不是构建系统能够自行决定的事情。
“接受”不是流水线自动计算出的技术真理。相关专业人员需要先说明变更的技术后果。随后,再由具备相应授权的责任主体,结合用途、适用环境、关键假设、剩余风险和现有证据作出决定。同一份制品可以用于内部实验,却未必具备用于正式产品发布的证据。
反过来,构建系统也不应该只是帮助项目寻找通过方式的工具。当一项约束无法落实、验证成本远超预期,或者新的结果推翻了原有假设时,工程流程应当迫使原来的决策重新进入评估。
例如,一次看似普通的公共组件升级,经过影响分析后发现需要重新验证几十种硬件组合。正确的处理方式不一定是继续增加机器、缩短测试或者申请跳过检查,也可能是重新讨论升级范围,拆分变更,或者延后部分产品的迁移。
决策产生约束,工程系统落实并验证约束;执行产生的新证据,又可能迫使决策重新评估。这种反馈能力很重要。没有真实的背压和否决机制,流水线就只是自动化手续,而不是工程控制。
构建工程师在建设什么
成熟的构建工程早已不只是维护 Makefile、Shell 脚本和 CI 配置。依赖图、增量构建、缓存一致性、工具链管理、跨平台兼容、可复现性和软件供应链安全,本来就是构建团队长期处理的问题。
AI 不会突然把构建工程师从脚本编写者提升成约束管理者。更准确的变化是:AI 会减少其中大量局部、手工和经验驱动的操作,同时放大构建工程原本承担的系统性责任。
这里需要区分人和系统。构建工程师不是控制面,构建工程才是自动化软件生产体系中的关键技术控制面。它不是全部软件治理,而是通过依赖关系、执行隔离、权限规则、测试要求、制品准入和证据记录等机制,把架构、安全和质量等方面能够自动执行的要求落实到软件生产过程中。
构建工程师负责设计、维护和演进这套机制。他们把安全、架构、产品维护和组织风险要求中能够工程化的部分,转化成生产过程里不能轻易绕过的规则;对于无法自动落实或者无法自动验证的部分,则负责把缺口明确暴露出来。
因此,构建工程师的价值不在于亲手完成每一次构建,也不在于不断为每个新产品复制一套流水线,而在于建设一套能够持续处理产品差异、环境变化和大规模自动化变更的工程体系。
过去,一些依赖关系和产品差异还可以依靠团队经验维持。AI 让实现和修改更快地出现以后,这种依靠人脑和临场处理的方式会越来越难以跟上。构建工程的重要性不是因为脚本变得更难写,而是因为整个软件生产过程越来越需要明确、稳定且不能被轻易绕过的控制。
结语
AI 可以比过去更快地产生变更,却不能自动让这些变更变得可接受。代码容易生成,不代表影响容易理解;实现容易重写,不代表系统变更容易撤销;Agent 能够自我检查,也不代表它可以自我证明。
工程的目标不是阻止变化,也不是建立一个永远不会失败的系统,而是让已知风险受到明确控制,让未知失效更难扩散、更容易暴露和追溯,并让组织在事故发生以后仍然能够恢复、学习和继续演进。
对于构建团队来说,AI 时代真正需要建设的,不是更多由人手工维护的脚本,而是一套能够持续回答这些问题的工程体系:
哪些因素可以影响制品?一项修改会使哪些产品和硬件配置对应的制品需要重新构建?预先要求的验证是否已经完成?哪些条件仍然无法自动证明?当证据不支持原有决定时,系统能不能真正阻止相关变更继续向前?
实现越容易产生,接受实现的条件越应该明确;自动化能力越强,边界、证据和否决能力越不能依赖临场判断。

Recent Comments