返回日报条目

资料节译

AI降低实现成本后,架构更需考虑验证成本

阅读原文

最近,我在工作中和同事讨论应该如何实现一项功能。我主张采用某种特定方案,原因之一是我认为它更便于 AI 代理处理。它的结构更明确,可能出现的状态更容易推理,也能让我们更好地自动化验证,并为今后的改动设置防护措施。

一位利益相关者提出了一个很有道理的反对意见:我们不应该仅仅因为某种设计方式更便于 AI 代理,就按这种方式设计软件。我们应该根据什么对用户最有利来设计。

我完全同意。

但这次讨论让我意识到,我的推理中有个重要部分一直没有表达清楚。我并没有把 AI 代理置于用户之上。我仍然是在寻找能让用户体验、可靠性、简洁性、上市时间、实施成本、运营成本和长期可维护性取得最佳平衡的方案。代理更容易理解和验证由此产生的系统,这只是权衡中的一个变量,而不是目标本身。

这个区别很重要,因为我认为 AI 正在改变软件架构的经济性,而我们还没有充分认识到这一点。

几十年来,软件开发中最大的制约因素之一是生产软件的成本。开发者必须理解问题、设计解决方案、编写代码、审查、测试、调试并维护代码。工程产能有限,因此减少所需的实现工作量极具价值。

AI 编码代理正在改变这种局面。它们可以生成实现、编写测试、重构代码、探索不熟悉的代码库、迁移 API,还能以仅靠人类很难达到的速度执行许多其他任务。

但这里有一个重要的不对称之处:生成代码和确认代码正确,并不是一回事。

代理可以在几分钟内生成一千行代码。但这并不意味着我们也能在几分钟内确认这千行代码正确实现了预期行为、没有引入回归、保留了重要的不变量,并且能与系统其他部分正确交互。

这意味着,随着生产代码的边际成本下降,另一项成本相对变得更加重要:验证成本。

而验证不是一次性成本。

最初构建软件时,我们要验证它;修改时要验证;重构时要验证;依赖项发生变化时要验证;其他功能与它交互时要验证;AI 代理修改它时要验证;修复缺陷时要验证;迁移数据时要验证;升级基础设施时也要验证。

实施成本通常大多是一次性的。验证成本则会反复发生。

这改变了架构权衡。

设想同一项功能有两种可选实现。

方案 A 的实施成本为 100,000 美元,但由于架构无法提供有力保证,之后每次改动都需要大量测试、审查和回归检查。

方案 B 的实施成本为 120,000 美元,但其架构能让许多无效状态变得不可能,并为系统行为提供有力的自动化保证。

如果只看实施成本,方案 A 似乎更便宜。

但初次实现完成后,软件并不会停止产生费用。

如果方案 A 每年还需要额外投入 20,000 美元用于验证和回归工作,而方案 B 只需 5,000 美元,那么最初节省的 20,000 美元很快就会被抵消。在系统的整个生命周期内,初始成本更高的实现最终可能成为成本更低的系统。

具体数字并不重要。重要的是,验证会反复发生。

因此我认为,我们需要开始把可验证性视为架构层面的考量。

比较两种架构时,我们不应该只问它们的构建成本是多少。还应该问,在系统整个生命周期内,我们能以多低的成本、多高的可靠性来确认它们是正确的。

我所说的验证,并不是指我们只要多写些测试就行。

测试非常有用,但还有一种更有力的验证方式:设计系统,让某些类别的错误行为从一开始就无法发生。

“我们有测试来检查这种无效状态不会出现”和“系统无法表示这种无效状态”,两者之间有巨大的差别。

后者是一种架构属性。

强类型就是一个简单的例子。如果函数接受的是 UserId,而不是任意字符串,那么类型系统就能在程序运行前排除整类错误。数据库约束可以阻止无效关系。状态机可以限制允许发生的状态转换。模式可以阻止格式错误的数据进入系统。幂等性可以消除整类重复操作错误。明确的边界可以减少组件之间可能发生的交互数量。

在这些情况下,我们都是把一部分验证负担从人和测试转移到系统自身的结构中。

系统本身提供了保证,因此更容易验证。

设想一个工作流,其中一份数据会经过多个状态。

它或许最初是 Created,随后变成 Validated,再变成 Processed,最后变成 Completed。

一种实现方式是使用单个事件表:

WorkflowEvents
id
workflow_id
event_type
payload
created_at

接下来,应用程序需要根据前一个状态判断某个事件是否有效。代码需要了解允许哪些状态转换、前一个事件是否存在、载荷是否符合事件类型,以及工作流是否进入了不可能的状态。

我们可以为所有这些情况编写测试,也可以编写应用层验证逻辑;还可以让 AI 代理推理状态机,并审查实现。

另一种方法是在持久化层更明确地建模工作流。我们可以针对不同事件设置专门的数据结构,让每个阶段通过外键引用前一阶段,并为该事件使用专门的模式。

概念上:

Created
   â
Validated
   â
Processed
   â
Completed

Validated 必须有对应的 Created 才能存在。Processed 必须有适当的 Validated 记录才能存在。Completed 必须有前一状态才能存在。

每种事件还可以有自己的模式,明确表达该阶段所需的数据。

此时,数据库不只是在存储工作流的状态,也在帮助约束工作流。

实际上,我们已经把有限状态机的一部分移入持久化模型。

我并不是说这永远是正确的设计。它也会带来自身的复杂性和权衡;对于许多工作流来说,更简单的事件表或另一种状态机实现可能更合适。重点不在于某一种架构普遍优于其他架构。

重点在于,架构选择会改变验证成本。

采用第一种设计时,我们可能需要反复通过应用逻辑和测试来确认无效状态转换不会发生。

采用第二种设计时,有些无效状态转换根本无法持久化。

这是截然不同的一种特性。

这是软件工程中的一个老观点,但 AI 让它越来越有价值。

如果我们能让无效状态无法表示,就不需要反复验证是否有人意外创建了这种状态。

想想下面两句话的区别:

“我们有测试证明这种状态永远不应该出现。”

以及:

“架构让这种状态不可能出现。”

第二句话有力得多。

这意味着,未来的开发者不必记住这条规则;未来的重构不必重新发现这条规则;AI 代理不必从一系列测试和文档中推断这条规则;代码审查者也不必留意某个细微的违规情况。

这项约束是系统的一部分。

这就是我所说的让验证成为架构层面考量的意思。架构不仅决定系统能够做什么,也决定系统可能做出哪些错误的事情。

这就是 AI 代理发挥作用的地方,但我认为论点不应是“为 AI 设计软件”。

更有力的论点是,AI 提高了软件变更的速度。

如果变更次数增加,验证这些变更的成本就会变得更加重要。

设想两个系统。对于第一个系统,每次改动都需要人重新构建复杂的心智模型,并判断某个细微的不变量是否得到保留。对于第二个系统,架构通过类型、模式、约束、契约和自动化检查表达了许多此类不变量。

第二个系统不只是更便于 AI 代理处理,也更便于所有人使用。

代理受益于更明确的边界和更清晰的反馈。开发者受益于需要记住的隐式行为更少。代码审查者受益于更多正确性属性得到机械化约束。运维人员受益于进入生产环境的无效状态更少。

AI 只是让这种差异更加明显,因为我们正在提高变更的数量和速度。

如果 AI 产生变更的速度快于人类安全验证变更的速度,那么能降低验证成本的架构就会越来越有价值。

这大概是最重要的经济层面的观点。

假设架构 A 的实施成本低 10%,但每次变更时都需要投入明显更多的精力来验证。

架构 B 的实施成本高 10%,但能提供强有力的结构性保证,大幅降低验证成本。

如果只看最初的项目,架构 A 可能显得更有吸引力。

但软件不是功能发布后就结束的项目,而是会持续变化多年的系统。

实施成本只发生一次,验证成本却一再发生。

每个基于该系统构建的功能都要承担一部分验证成本。每次重构、迁移和 AI 生成的改动也都要承担这部分成本。

这会产生复利效应。

少量额外复杂性若能换来强大且可重复使用的验证保证,或许能多次收回成本。

因此我认为,架构讨论需要考虑验证在整个生命周期中的经济性,而不应把测试和验证当成架构决策完成后才进行的事情。

我们已经会用性能、可扩展性、可靠性、安全性、可用性和可维护性等质量属性来讨论架构。

我认为我们应该越来越多地提出另一个问题:

这个系统有多容易验证?

而不只是问:

“编写测试有多容易?”

而是:

“我们能以多低的成本、多高的可靠性来确认这个系统运行正确?”

这两个问题并不相同。

一个系统即使测试覆盖率很高,仍可能难以验证。它可能有复杂的状态转换、隐式依赖、薄弱的契约、高度耦合的组件,或许多可能出现的无效状态。

反过来,系统也可以被设计成让其结构本身提供有用的保证。

这可以是强类型、数据库约束、显式状态机、精简接口、确定性组件、模式、事务边界、幂等操作,或其他形式的架构约束。

它们的共同点是,确认正确性的成本更低。

我认为这也改变了我们评估不同方案的方式。

过去,我们可能会问:

这些问题仍然重要。

但我认为我们还应该问:

有意思的是,这些问题可能会改变最终答案。

如果一种方案能大幅降低反复发生的验证成本,那么即使它的实施成本更高,在经济上也可能更划算。

这并不意味着约束最多的架构总是胜出。约束也有代价。过多的约束会让系统变得不必要地僵化、难以演进,或者比问题本身所需的更加复杂。

重点不是把约束增加到最大。

重点是认识到,当约束能够降低确认正确性的成本时,它们就具有经济价值。

回到那次工作中的讨论,我仍然认同那位利益相关者的原则:用户应该优先。

但我现在认为,我们在判断什么对用户最有利时,漏掉了一个维度。

问题不是:

“哪种架构最便于 AI 使用?”

也不只是:

“哪种架构的实施成本最低?”

更准确的问题是:

“考虑到构建、运营、改动和验证系统的全生命周期成本,哪种架构能给用户带来整体上最好的结果?”

有时答案会是尽可能简单的实现。

有时答案会是最便于 AI 代理修改的架构。

有时答案会是前期复杂得多的架构,因为这份复杂性带来的保证,否则就必须反复验证。

有时正确答案会完全不同。

重要的是,可验证性如今也是权衡的一部分。

人们常从能多快生产软件的角度讨论 AI 辅助开发。

我认为这只讲了一半。

如果 AI 大幅降低了实施成本,那么日益稀缺的资源就会变成确认我们产出的东西正确的能力。

这意味着,系统架构不再只是让预期行为能够实现,也要让错误行为难以表达、容易发现,并且成本低廉地排除。

因此,最值得关注的架构决策,或许是那些能让验证从反复发生的人工作业转变为系统自身属性的决策。

因为最终目标不是构建 AI 能快速生成的软件。

而是构建我们能够持续确认其正确性的软件。

如果我们能设计出有助于实现这一点的架构,那么验证就不再是开发结束时才附加上去的事情。

它会成为架构的一部分。