现在,一个应用从需求描述到完成测试、打包和部署,可能只需要几分钟。

开发者不必逐页查找文档,也不必亲手写完每一段脚本。AI 可以识别项目结构、安装依赖、生成流水线,根据执行结果修复错误,最后把应用部署到一个真正能够提供服务的环境里。

站在完成任务的人的位置上,许多过去熟悉的软件工程工作确实像是消失了。原来要花几天完成的事情,现在可能只剩下一句目标和几轮确认。

这种变化是真实的。大量人工实现和操作会减少,以这些操作为主要价值的岗位也可能随之收缩。平台和 AI 甚至可能真正降低软件生产的总成本,而不只是把同样的工作转移给另一批人。

但这里要讨论的,不是未来还需要多少软件工程师,也不是为了证明软件工程成本一定会上升。真正的问题是:

当一份实现需要长期运行、保存状态、被其他系统依赖,并开始对现实产生后果时,哪些问题会因为实现和部署变简单而消失,哪些仍然需要平台、组织或者自动化机制继续处理?

前一篇文章选择构建工程作为切口,是因为这里很容易看见一个转折:一份可以随时删掉重写的代码,怎样开始变成需要测试、发布、维护并承担后果的软件。

让我们继续往下追:一份可以随时重新生成的实现,怎样开始碰到状态、依赖、权限和责任关系,并因此越来越难被直接替换。

一次成功只说明一条路径走通了

假设 AI 为一个应用生成了部署脚本,拉取运行依赖,完成测试和打包,最后一次部署成功。

这次成功至少说明,在当时的代码、依赖版本、网络条件、权限配置和平台状态下,存在一条能够完成任务的路径。

对于一个短期使用、风险很低的内部工具,这可能已经足够。软件工程并不要求每个临时程序都建立完整的供应链证明、跨环境验证和长期维护体系。应该做到什么程度,本来就取决于软件的用途、寿命和失败后果。

但是,一次成功没有自动回答接下来的问题。

代码修改以后,还能不能得到符合要求的结果?依赖升级会不会改变程序行为?外部服务不可用时,应用会怎样退化?这次成功是否依赖环境中某个没有声明的软件包?部署脚本实际获得了哪些权限?数据能否迁移和恢复?新版本还能不能读取已有状态?

这些问题不一定都需要开发者亲自处理,也不必在第一次部署以前全部解决。但只要软件开始被持续使用,它们就不会因为第一次部署很顺利而消失。

这里容易混淆两件事。

第一件事,是实现并执行一条已知路径的成本正在快速下降。

第二件事,是让软件在持续变化、故障和长期运行中仍然保持可理解、可控制和可恢复的成本,也在同步下降。

前一个变化已经非常明显。后一个变化也可能通过平台化、标准化和进一步自动化发生,但不能只根据一次部署成功得出结论。

平台确实会让一部分工程消失

AI 只靠一句话就能完成部署,并不只是因为模型变聪明了。

它还依赖一个已经被高度工程化的环境。

云平台把计算资源、网络、证书、域名、容器运行、日志、扩缩容和权限控制整理成稳定接口。包管理系统规定依赖怎样描述、解析和下载。应用框架约定项目结构、启动方式和配置位置。CI 系统提供可以反复调用的执行环境。平台 API 又把这些能力组合成可以被程序控制的操作。

AI 面对的并不是一个完全开放、没有结构的现实世界,而是一组经过长期设计、能够接收明确输入并给出明确反馈的工程接口。

平台也不只是把应用团队原来的工作搬到另一层。

一个托管平台统一处理证书签发和续期以后,不需要再为每个应用维护一套相同的人工流程。平台只允许少数经过验证的部署方式时,大量自定义脚本、环境组合和相应故障也会直接消失。

平台最有效的地方,往往不是把一百种部署方式全部自动处理,而是让其中大部分方式不再成为可选项。

应用团队不再管理物理服务器,也不意味着平台团队逐台接管了同样数量的手工操作。标准化、共享实现和多租户平台可以同时降低人工成本和故障率。

但平台没有标准化的关系仍然存在。

数据状态、业务权限、外部依赖、兼容要求和特殊运行条件,不会因为部署接口变简单就自动得到处理。平台本身还可能引入新的供应商依赖、控制面复杂性和不可见的内部状态。

所以,更准确的问题不是“复杂性被搬到了哪里”,而是:

哪些问题已经通过标准化被消除,哪些被共享和摊薄,哪些只是换了处理位置,平台又引入了哪些新的关系?

当同样的 AI 进入旧系统、专有网络、特殊硬件、历史数据和内部安全规则大量存在的环境时,效果往往会明显下降。

这不一定是 AI 突然变笨了。更常见的原因是,这些环境里仍然存在许多没有被标准化的关系:真实依赖没有完整记录,事实来源并不可靠,各个系统之间又有足够大的差异,不值得或者无法被统一成一套接口。

平台能够消除的,是已经被标准化的选择、路径和重复工作。

剩下的问题,主要来自某项具体变更与既有状态、依赖、权限和责任之间的关系。实现一旦开始碰到这些关系,就不再只是一个可以随时删掉重来的方案。

一份实现怎样变成系统中的一次变更

前一篇文章用“实现可以重写,系统关系不能一键重来”概括了这种不对称。

AI 刚生成的一段代码,通常很容易替换。发现方向不对,可以删掉重做;换一种实现,代价也可能不大。

这里可以把这种还没有被系统接受、仍然能够自由替换的方案称为“候选实现”。

真正困难的部分,出现在候选实现准备进入一个已经运行的系统时。因为这个系统并不是空白的,它已经形成了两类关系。

第一类是已经存在的系统事实。

数据已经按照某种格式写入,某个行为已经部署并被用户使用,组件之间已经建立依赖,接口也已经被其他系统实际调用。这些事实会直接影响后续的兼容、迁移、恢复和验证。

第二类是系统对外形成的承诺。

公共接口、客户合同、交付计划、用户预期,以及监管、认证和合规义务,都可能限制系统以后可以怎样变化。有些承诺甚至会在第一行代码出现以前就已经成立。

系统事实和外部承诺形成的时间并不同步。

一段代码可能已经进入生产制品,但仍然被功能开关隔离。它没有写入数据,也没有被外部用户使用。此时虽然发生了部署,新增的系统事实和外部承诺仍然有限。

反过来,一个功能可能尚未开始实现,但公共接口、交付合同和合规要求已经确定。代码仍然可以自由选择,系统未来必须满足的条件却已经增加。

所以,一次发布并不是所有约束同时生效的分界线。

当一份实现必须连同已有数据、依赖关系、权限边界和外部承诺一起接受影响判断时,它就从候选实现变成了面向既有系统的“候选变更”。

候选变更首先要面对已经存在的事实和承诺。它如果被系统接受,还会继续写入新的数据、建立新的依赖、形成新的行为预期,并留下新的长期责任。

我们可以把一项变更当前需要处理的这些影响称为“变更负荷”。

变更负荷不是代码改了多少行,而是这次修改碰到了哪些已有关系。这些关系是否包含重要状态,能不能快速撤销,影响是否跨越多个系统,又需要多少验证和协调,都会改变实际负荷。

把变更接受以后留下的长期义务称为“承诺成本”。

它可能来自新的数据格式、公共 API、消息协议、权限模型、用户行为预期或者合同义务。以后要继续维护、修改或者退出这些关系,就需要承担兼容、迁移、验证、协调和责任成本。

变更负荷和承诺成本通常有关,但并不是同一件事。

一次内部重构可能需要修改很多底层代码,当前变更负荷很高,但如果它没有改变数据、接口和外部行为,就未必增加多少长期承诺。

一个公共 API 第一次实现起来可能很简单,但一旦被外部用户依赖,未来修改和退出它的成本就可能很高。

今天被接受的变更,会成为明天系统的一部分。它写入的数据、建立的依赖和形成的承诺,会变成下一次变更必须面对的既有条件。

长期软件正是在一次次接受和再次修改中形成历史的。已有关系可以被迁移、退役或者结束,但通常不能无成本清空。

这并不只发生在有固定硬件的系统中。

硬件会带来资源、时序、产品变体和认证限制。纯软件系统虽然没有同样的物理约束,却会逐渐积累数据格式、公共接口、消息协议、权限模型、合同和制度要求。

这些约束形成的原因不同,但都会减少系统以后可以自由选择的空间。

工程的作用因此不只是证明一个实现能够工作。它还要识别已有承诺,避免不必要的关系过早固定下来,把高风险变更拆成可以观察和撤销的步骤,并在必要时保留兼容窗口、迁移路径和退出方式。

这些过渡结构本身也有成本。兼容层、双轨运行和分阶段迁移会增加临时状态、额外观测和旧路径清理工作。只有在它们保留下来的选择空间足够有价值时,这些复杂性才值得承担。

当然,这些机制无法证明需求本身是正确的。把错误的目标稳定、可靠地做进系统,仍然是在做错事情。

“应该做什么”与“怎样把它可靠地带进一个长期运行的系统”,是两个不同的问题。本文主要讨论后一个。

实现变便宜以后,变更吸收为什么更容易成为主要成本

AI 最直接的变化,是让候选实现的生成成本下降。

它同样可以帮助分析、测试、调试和运维,但这些工作更多受到已有状态、外部依赖和证据要求的限制。AI 可以快速写出一个数据库迁移脚本,却不能因此消除已有数据。它可以生成一套兼容方案,却仍然需要知道哪些系统实际依赖了旧行为。

前文把工程体系处理这些问题的能力称为“变更吸收能力”。

它指的是:工程体系能否看清一项变更会碰到什么,为关键判断建立足够证据,并在该继续、该调整和该停止时作出明确处置。

这里处理的不是一组完全相同、可以按提交数量计算的修改。

一百个彼此隔离、能够自动验证并快速回退的小修改,可能比一次涉及历史数据和公共接口的迁移更容易吸收。

反过来,一个发布频率很低的系统,也可能因为一次协议或者数据库格式变更,承担巨大的验证、协调和迁移成本。

多个小变更还可能相互影响。它们单独看都很安全,组合以后却可能扩大依赖版本范围、引入配置冲突,或者同时影响同一批下游系统。

而且,变更负荷通常只能在开始以前粗略估计。真正的构建、验证和运行结果,可能暴露原来没有发现的依赖和失效方式,迫使工程体系重新判断影响范围。

即使工程体系还没有超载,实现成本下降得更快以后,影响分析、验证和风险处置也可能先成为一项变更的主要成本。

过去主要花在写代码上的时间,现在可能更多地花在几个问题上:这次修改究竟影响什么,需要什么证据,失败以后能否恢复,以及它是否应该继续进入系统。

只有当持续到来的变更负荷接近或者超过某类处理能力时,问题才会进一步表现为排队、降低准入标准和长期复杂性积累。

这里还要注意,工程能力并不是一个统一的总容量。

数据库迁移、安全评估、硬件验证和跨团队协调依赖的是不同能力。整个研发体系看起来仍有余量,不代表每一类变更都能被及时处理。

完整地说,变更吸收能力包括三件事:

理解候选变更会触动哪些已有关系;为这些影响建立与风险相称的证据;判断变更被接受以后会形成哪些新的事实和承诺,并据此决定接受、拆分、推迟、缩小范围、拒绝或者重新设计。

工程体系不会独自决定所有产品、合同和组织承诺,但它需要把一项变更会怎样兑现、扩大或者违背这些承诺说明清楚,也要阻止未经授权的后果悄然进入系统。

因此,变更吸收能力不能用流水线每小时处理多少任务来衡量,也不能简单看放行率或者拒绝率。

如果工程流程只会帮助每项变更寻找通过方式,却没有真实的背压和否决能力,发布数量可能提高,系统承担的长期复杂性也会继续增加。

反过来,如果标准路径只会拒绝真实存在的必要差异,复杂性也不会消失。团队会转向私有脚本、人工操作和平台看不见的例外路径,最终让整个组织更难理解系统实际是怎样运行的。

好的工程体系要让可以接受的变更及时前进,让不合适的变更尽早改变形状或者停止,同时不稀释原有的权限边界和证据要求。

平台和架构会同时影响变更负荷、吸收能力和未来的承诺成本。

标准化接口、限制可选状态和建立稳定模块边界,可以减少一项修改需要触动的关系。自动测试、执行隔离、影响分析和恢复机制,可以提高工程体系建立证据和处理失败的能力。

更好的设计还会直接改变变更本身的形状,让它更局部、更容易观察,也更容易停止和撤销。这样既能降低当前负荷,也能避免系统形成不必要的新关系。

工程体系需要管理的三类边界

要吸收一项变更,工程体系至少需要回答三个问题:

什么可以影响结果?

现有证据究竟证明了什么?

谁有权让这件事继续发生?

这三个问题分别对应因果边界、证据边界和决策边界。

什么可以影响结果:因果边界

因果边界关心的是,哪些输入、状态和外部条件被允许影响制品或者运行结果。

源码、依赖、工具链、配置、网络资源、模型、数据和可调用工具,都可能进入实际的因果链。

把这些输入写进声明,只是第一步。真正重要的是实际执行是否仍然遵守这条边界。

如果构建脚本可以读取未声明的宿主文件,可以直接调用环境中碰巧存在的工具,或者在受控入口之外访问网络,那么声明中的输入就不等于真正影响结果的输入。

Agent 系统也是如此。系统可能声明 Agent 只能使用几种工具,但如果它还能绕过这些工具直接访问其他资源,实际因果边界就已经超出了声明。

因果边界必须能够被执行、观察和验证,不能只存在于配置文件或者设计文档里。

现有证据究竟证明了什么:证据边界

一项测试、评测或者运行记录,并不笼统地证明“这个软件没有问题”。

它只能支持一个更具体的结论:哪个对象,在什么环境和时间下,满足了哪些实际被检查的条件。

首先,证据要绑定到身份明确的对象。

前文强调的“确定的制品”,就是为了这一点:测试、安全检查和发布记录需要说明自己针对的是哪一个制品,以及这个制品从哪里来。让同一个正式制品依次进入测试和发布阶段,是降低对象识别成本的一种常见做法,但这并不意味着制品在物理上只能构建一次。

其次,证据不能在不同对象之间随意转移。

可复现构建解决的是:另一方获得相同源码、相同构建指令,以及项目声明为相关的环境条件以后,能否重新得到逐字节一致的制品。

如果两个阶段产生的是不同制品,就需要说明它们在哪些性质上可以被视为等价。

两个制品功能行为相同,不代表它们的性能、安全属性和来源也相同。分别通过同一组测试,也只能说明它们满足了这些测试实际检查的条件。

证据能够从一个对象转移到另一个对象的范围,不能超过原有证据检查的内容,也不能超过等价证明覆盖的性质。

最后,证据还需要必要的独立性。

同一个 Agent 根据同一份需求理解,连续生成实现、测试和说明,整个过程即使可以追溯,也不等于已经形成了相互独立的证据。

问题不只是是否换了另一个模型。真正需要检查的是,关键证据会不会因为同一个错误假设而一起失效。

对于风险较高的变更,不能让所有证据都依赖同一份需求解释、环境假设、数据来源或者测试判定方式。

对于影响有限、容易发现并且容易恢复的问题,同一个 Agent 的自我检查仍然有价值。证据要求应该与后果相称,而不是机械地追求形式上的独立。

谁有权让它继续发生:决策边界

决策边界关心的是,谁有权接受哪些剩余风险,又有权授权哪些外部副作用。

构建系统、策略引擎和其他自动化机制可以执行已经确定的规则。例如,测试失败就停止发布,权限不足就拒绝操作,金额超过限制就要求额外授权。

但自动化系统不能因为自己找到了一条能够通过的路径,就替真正承担后果的人或组织决定某项风险值得接受。

有权决定项目进度,不代表有权豁免合同、安全或者监管义务。拥有生产环境操作权限,也不代表有权改变所有业务数据。

如果风险最终由谁承担、谁有权决定本身就不清楚,这种不确定性也应该被暴露出来,而不能由流水线或者 Agent 默默替组织作出决定。

这三类边界并不会因为写进文档就自动变得正确。

在遗留系统里,真正危险的往往是没人知道的依赖、无法完整枚举的产品变体,以及只存在于少数人经验中的历史假设。

此时第一步不是假装系统边界已经完整,而是围绕后果较大的变更,通过隔离、观测、影子运行、失败反馈和逐步建模,发现原来没有表达出来的关系。

明确边界不等于已经完全掌握真实系统。它至少要求工程体系把已知的未知、信任假设和验证缺口记录下来,而不是默认它们不存在。

因果边界决定当前能够表达、观察和限制哪些影响。

证据边界决定这些影响被理解和验证到了什么程度。

决策边界决定在仍有不确定性时,谁有资格让变更继续。

三者共同构成一项候选变更被接受、调整、推迟或者拒绝的基础。新的运行结果一旦暴露出未知关系,原有判断也需要重新评估。

AI 系统怎样改变这些边界的表达和范围

AI 没有创造一套完全不同的软件工程问题,但它改变了这些边界的表达方式,也扩大了需要管理的因果范围和现实后果。

交互方式会改变,精确表达不会消失

随着模型能力提高,人通过编程语言逐句描述实现的比例很可能继续下降。

在标准化程度较高的领域,自然语言、图形化描述、示例和已有系统状态,可能成为人与软件生产系统交互的主要入口。代码更多地由 AI 生成,再交给工具执行和检查。

但因果、证据和决策边界不能只存在于一次临时对话中。

一句“把这个应用部署到内网”,对于人来说可能已经表达了主要目标。对于执行系统来说,仍然需要确定部署到哪个环境、使用什么身份、允许访问哪些网络和数据,以及失败以后怎样处理。

AI 可以推断这些条件,但推断本身就是一次实际选择。不同推断可能产生完全不同的权限范围和现实后果。

这也不意味着系统的所有行为都必须固定。

推荐、调度和交互式 Agent 本来就可能允许动态甚至概率性的结果。真正需要稳定表达的,是会影响系统风险、外部副作用、兼容承诺和责任判断的条件。

允许变化的部分,也需要说明变化范围和不可突破的限制。

编程语言可以逐渐退到交互界面的后台,但精确、稳定、可检查的表达能力不能消失。否则,每次执行都相当于重新解释一次意图。

Agent 运行时扩大了因果和证据范围

传统程序的大部分行为可能已经包含在编译后的制品里。

以 Agent 为执行节点的系统,最终行为还可能取决于模型、系统提示、检索数据、可调用工具、外部服务状态、当前时间、会话状态和运行权限。

即使代码和制品完全没有变化,其中任何一个运行时条件改变,都可能得到不同结果。

构建过程和运行时过程都需要管理来源、授权和追溯,但它们不能使用完全相同的保证方式。

封闭构建可以限制输入、隔离环境,并追求确定的制品或者可复现构建。

开放、概率性的运行系统会持续接收外部输入,与不断变化的数据和服务交互,还可能产生无法撤销的外部副作用。即使记录了模型、提示和检索内容,也未必能够重新得到完全相同的执行轨迹。

因此,这类系统需要管理的不是全面确定性。

有些条件必须成为不能突破的硬约束,例如权限范围、金额上限和禁止访问的数据。

有些性质只能通过统计方式判断,例如错误率、危险动作发生率和结果分布。

工程体系还需要提前定义:什么情况下停止高风险动作,什么时候降级到已经验证的路径,何时隔离某项能力,以及哪些变化会触发重新评估。

这不等于任何异常都要停止整个系统。不同后果需要不同的停止和降级条件。

运行时证据也有明确的对象、环境和时间范围。模型升级、外部 API 改变、数据分布变化、权限扩大或者新工具接入以后,过去的测试和评测可能已经不足以支持原来的结论。

证据记录本身还受到隐私、敏感数据和保留成本的限制。目标应该是为具体工程判断留下足够证据,而不是无限记录所有可能影响结果的内容。

Agent 把决策边界直接连接到现实动作

当 Agent 能够搜索资料、修改配置、调用服务和执行多步操作时,模型输出就不再只是文本,也可能成为现实动作的起点。

模型可以提出动作,但不能由它自己决定这个动作是否有资格发生。

系统需要预先规定,哪些来源和通道具有指令权。即使不可信内容影响了模型提出的动作,也不能因此获得新的执行资格。

具体工具调用及其参数,仍然应该经过模型之外的权限和策略判断。这套判断不能被同一个 Agent 任意修改或者绕过。

策略执行机制不必先证明一段自然语言在语义上“不是指令”。它需要保证,无论模型怎样理解这段内容,都不能因此突破已经确定的能力、参数和副作用边界。

当然,外部策略本身也不会天然正确。它同样需要版本化、测试、观测和审计。

它的价值在于把动作生成与动作授权分开,避免同一次模型误判同时控制目标解释和最终授权。

权限正确也不等于动作正确。

一个 Agent 可以在合法权限内误解目标,也可能因为重试而重复执行一个不能安全重复的操作。风险取决于它的自治程度、权限范围、行动频率、影响范围和可撤销性,而不只是模型看起来有多聪明。

外部机制不能证明业务意图本身正确,但可以限制错误动作的范围,为高后果操作增加独立判断点,并阻止一次模型误解直接变成不可控制的现实后果。

AI 降低了提出和执行动作的成本,也让工程体系需要管理的因果、证据和决策边界,从发布前的变更控制延伸到了持续运行过程。

哪些工作会消失,什么时候不需要更多工程

讨论软件工程还在解决什么问题,没有必要否认大量工作会被自动化。

按照文档组合已有组件,编写常见部署脚本,修复普通依赖问题,根据日志反复调整环境,这些工作已经越来越适合交给 AI。成熟平台提供足够明确的接口以后,人甚至不需要理解其中的大部分实现细节。

如果一个岗位的主要价值长期停留在这些操作上,它确实可能被压缩,甚至不再需要。

软件仍然需要可靠性、权限控制和恢复能力,也不能因此推出每个应用团队都要保留原有数量和分工的岗位。

这些能力可以集中到平台层,也可以通过共享机制和统一控制降低每个应用的边际成本。很多过去由各个团队分别承担的工作,会被平台一次性解决。

同样,并不是每个软件都值得建立完整的控制机制。

一个寿命很短、没有重要状态、影响范围有限、失败以后可以直接删除重来,也不需要兼容旧版本的工具,采用很轻的工程控制可能就是合理选择。

工程不是把每个系统都做得尽可能复杂,而是根据寿命、状态、依赖、权限和失败后果,决定哪些关系值得被明确管理。

临时工具真正危险的地方,通常不是它一开始少做了工程,而是它在被长期使用、开始保存重要状态或者获得更高权限以后,仍然继续按照临时工具对待。

随着软件承担的关系和现实后果增加,原来可以忽略的条件会逐渐变得需要表达、验证和控制。

结语

AI 和平台可以真实降低,甚至消除大量实现、测试和部署成本。软件工程不需要把一句目标重新展开成更多人工步骤,来证明自己仍然存在。

当实现和部署不再稀缺,软件工程更需要处理的是:一份可以随时重写的实现,怎样成为面向既有系统的一次变更;它会触动哪些已有事实和外部承诺;被接受以后,又会留下哪些新的长期关系。

工程体系需要理解这项变更当前带来的负荷,判断它会形成什么新的事实和承诺,并通过因果、证据和决策边界,决定应该接受、调整、推迟还是拒绝它。

实现可以越来越容易重来,但一次接受所形成的关系,会成为下一次变更必须继续面对的历史。

软件工程要管理的,正是这段历史怎样形成、怎样改变,以及它的后果最终由谁承担。

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.