写点什么

将可理解性作为架构特性:无法理解的系统无法安全演进

作者:Jacobus Meintjes, Narayana Rengaswamy, Paul Katsande, Sureshb
  • 2026-08-20
    北京
  • 本文字数:5435 字

    阅读完需:约 18 分钟

本文由 InfoQ 认证架构师在线活动的参与者撰写。这是他们工作的集大成之作,反映了该活动的参与者在 AI 与现代软件架构的交叉领域中所获得的集体见解。

可理解性问题

在复杂系统领域有丰富经验的团队,想必都曾遇到过处理生产环境故障的情况:在处理过程中,大部分时间都花在弄清楚系统的各个部分如何连接以及系统实际做了什么操作上,而不是查找 Bug。最终,团队只能通过翻阅代码来厘清依靠团队集体记忆无法明确回答的问题——即系统的行为方式。出现这一问题的原因在于,对系统的理解从未超越某些特定的个人,也未能扩展到团队中的其他成员。

在演进式架构中,系统应该设计成可以吸收并适应不断变化的需求和环境。但要真正实现这种适应性,团队成员需要对系统本身有深刻的理解。正如 Peter Naur 在《编程即理论构建》一书中所论述的那样,仅仅理解代码是不够的;你必须理解构建该代码的程序员心中所秉持的底层“理论”。这里的“理论”是指程序员为理解程序运行机制而构建的心理模型。他指出,系统不仅包含代码,还包含这一理论。因此,该理论在团队中被接受的广泛程度和持久性是系统本身的一个属性。Margaret-Anne Storey 在其论文“从技术债务到认知债务与意图债务:人工智能时代软件健康的新思考”中进一步阐述了这一观点。她在文中定义了三种截然不同的系统债务:技术债务、认知债务和意图债务。认知债务是指对系统的共同理解悄然流失(即诺尔所说的“理论”流失);意图债务则是缺乏对系统现状形成原因的合理依据。与性能和可用性等特性不同,这一特性会随着时间的推移悄然恶化,而一个不被理解的系统无法安全地演进。由于人类的理解既包含“是什么”也包含“为什么”,所以它是解决认知债务和意图债务的直接解药,必须被视为演进式架构中固有的架构特性。

削弱可理解性的三大因素

在由多个团队维护的复杂系统中,集中式决策往往会导致瓶颈和知识孤岛。决策需要经由少数人逐一审批,知识也因此集中在了这些人手中。为消除这些瓶颈并真正地实现演进式架构,组织可以转向去中心化的架构决策模式。虽然这能优化流程,而且各团队能在各自的子领域中积累起深厚的专业知识,但整体视图会因此变得支离破碎。各团队可能会了解自身服务的运作方式,却可能忽略了系统边界最初划定的“原因”。在缺乏共同治理和实践的情况下,各团队的理论会逐渐背离,导致组织内部的知识碎片化加剧。

团队人员流动也加剧了这一问题。当人员离开团队时,他们会带走部分“理论”。新入职者必须从头重建理论体系,而现有的文档通常只记录“是什么”,而非“为什么”。由于缺乏关于某些边界为何存在的历史背景,所以他们可能会退而求其次,采取战术性修补,而非进行符合原始设计的系统性改进。久而久之,这将侵蚀架构的完整性。

在这三股力量中,最新且发展最快的是生成式人工智能(GenAI)。它减少了以往为获得可理解性这一副产品所需的实现工作量,但正是这些工作量在开发者心中构建起了系统的思维模型。在生成式人工智能于软件工程领域崭露头角之前,可理解性是在设计、实现和验证阶段自然形成并得到强化的。随着这项技术的日趋成熟,交付压力也比以往任何时候都更加严峻。我们发现,这种“不理解就交付”的倾向正悄然渗入企业级软件领域。在向客户进行功能演示时,我们团队中一位经验丰富的工程师不得不花费大量的时间来理解她一周前交付的一段代码是如何工作的,因为她从未建立起对这段代码的心理模型。虽然这段代码通过了所有的质量检查,但在演示过程中,“理解债务”才首次显现出来。

Arvind Narayanan 和 Sayash Kapoor 最近发表的一篇文章指出,软件工程师的工作如同一个“决策-执行-交付”的三明治,而理解是这三层的先决条件。

生成式人工智能压缩了中间层。我们认为,过去贯穿这三个层的理解,现在应当有意识地在两端构建。对于系统中比较复杂的部分,其可理解性必须在这两个阶段形成——即“生成”阶段之前和之后。软件工程师必须在这些阶段投入精力,将设计与现有系统的结构联系起来,组织并构建一个心理模型。

知识碎片化、团队人员流动以及 AI 引发的变更——这些因素以不同的速度侵蚀着可理解性,最终导致系统无法安全地进行变更。

检测和度量可理解性的流失

架构师和技术负责人可以通过监控若干关键指标,量化系统可理解性的流失。实现这一目标的天然工具是适应度函数——它们使架构师能够表达重要的架构特性并自动进行验证。自动化的适应度函数可以度量理论流失的一些先行指标,但无法度量对设计意图本身的理解程度。没有任何管道检查能确认人类是否理解某项变更存在的理由。自动化任务是检测理论在什么条件下出现了衰减;而验证变更是否符合设计意图,以及是否有人真正地掌握了其背后的理论,则属于下一节所述的人工检查点。

因此,我们有意扩展了该术语的内涵。下文列表中的“适应度函数”评估的是围绕代码的社会技术系统,其中只有一部分可以完全自动化,其余的则是监测指标而非可执行的检查。在某些情况下,这是主动选择而非限制。关于人的指标在强制执行时会改变行为——阻挡 PR 的门槛可能会被钻空子绕过——当钻空子很难或无害时才强制执行,否则仅进行监控。将阈值视为调查的触发点,而非目标。当存在可绕过的门槛时,应追踪豁免和覆盖情况,以便修正阈值或改进团队实践。

图 1:可理解性必须在执行阶段之前和之后形成(图片由作者使用 draw.io 制作)

代码审查动态

拉取请求(PR)是知识共享的主要途径。审查指标出现异常往往意味着共同理解出现了问题。请留意以下早期预警信号:

  • PR 规模过大:当 PR 规模过大以至于无法进行审查时,其本应承载的知识流动便无法实现。可以通过阻止过大的 PR 来落实这一要求(根据具体情况定义阈值)。请作者将 PR 拆分,并在设计审查阶段尽早发现这一问题。

  • 智能代码审查工具:不要让智能代码审查成为 PR 的唯一审查者——它可以作为第一位审查者,但必须由人工审查者进行后续审查。可以通过“代码所有者”(CodeOwners)等策略来落实这一要求。

  • 代码审查失效:当审查工作量集中在少数人身上,或者组织氛围导致审查人员不敢向资深成员提供恰当的反馈时,审查便沦为走过场。这种情况只能通过监控来发现。追踪已获批准的 PR 中未附有实质性评论或仅标注 “LGTM” 的比例,以及团队内审查任务的分布情况。轮换审查人员,并赋予团队中每个人(无论职位高低)审查 PR 的权限。要明确传达一个理念,鼓励对任何作者的设计或代码提出质疑,包括架构师的。

  • 设计评审缺失:对于影响范围广泛的代码变更——即本质上比较复杂的核心模块和接口——无论变更规模大小,都应该先进行设计评审。由于这一点难以强制执行,所以应追踪那些涉及核心模块且未经事先设计评审就被合并的 PR。要求作者就设计变更向团队作一次说明,以弥补缺失的设计评审。

知识分布

当关键的上下文信息被极少数工程师垄断时,项目就会陷入停滞。要警惕那种只有一个人掌握系统设计意图的情况。听到“我们等 Dave 吧”这种说法时,团队应该立即提高警惕。作者贡献度(DOA)衡量的是个人在系统创建过程中所完成的工作量。较高的 DOA 意味着知识集中在少数人手中。请注意,DOA 存在局限性;应该将其看成是可参考的众多信号之一。可借助 git-truck 等工具进行监控,映射代码库中的知识分布,并揭示严重的单点故障。“卡车系数”低表明系统的可理解性较为脆弱。应对负责此类模块的开发人员进行轮换,为这些模块指定多名负责人,并组织学习培训。

入职摩擦

新员工达到正常工作效率所需的时间,是衡量系统可理解性的直接指标。如果入职适应期有延长趋势,那么该项目的认知负担很可能也正在给资深团队成员带来压力。应追踪新员工从入职到能够为系统设计和架构做出贡献所需的时间,而不仅仅是提交拉取请求。鼓励新入职的工程师记录那些令人困惑的问题——例如文档缺失、契约过时、隐式依赖关系等。定期审查并解决这些问题。

意图文档缺失

如果设计决策或变更没有附带相应的“原因”说明,那么随着时间的推移,甚至连创建者自己都会感到困惑。文档正是对“将来未参与该项目的人是否需要它”这一问题的解答。要追踪此类问题的根源,可以检测拉取请求(PR)在未添加 ADR 或理由说明文档的情况下触及核心或架构边界。同时,也要追踪那些因修改现有模块而需要追溯缺失背景信息的案例。解决方法是添加轻量级的 ADR 来记录设计意图。

领域泄露

那些破坏架构边界的变更可能会使“局部推理”变得不可能,而一个看似孤立的变更会在无关的领域产生意想不到的副作用。请使用架构适应度函数,如果违反约束条件,就让构建过程失败;而且,如果出现这种情况,请重新设计接口或依赖关系。

可理解性检查点

人工审查者不能仅仅通过阅读代码就轻信代码对自身的描述。在这个阶段,人工审查是可理解性的一个检查点,而非质量把关环节。像适应度函数这样的反馈传感器应当能捕获错误的输出,而人工审查者的作用在于把握设计意图,构建关于该功能变更及其原因的理论框架。这就是为什么在智能工程流程中,设计审查比代码审查更为重要。

工程师在设计和实现过程中理解一个模块,与在模块生成之后理解它,两者之间存在一个核心的区别——这就是主动思考与被动思考的区别。当你在没有现成解决方案的情况下主动解决问题时,会产生与具体情境相关的创新性解决方案和想法。这有助于系统以有机的方式演进,其驱动力来自与业务领域和约束条件相关的决策。如果代码审查和验证是唯一需要我们理解的环节,那么真正的理解就永远无法形成。事前理解能确保人类始终掌控正在构建的系统。事后理解则意味着你将不再决定系统的设计;由于系统可以以多种方式构建,如果你不在前期做出决策,最终得到的将是大语言模型(LLM)在训练中形成的统计学上的默认设计,而非你的具体情境所要求的方案。

这并非意味着每次变更都必须遵循这一流程。将繁琐的工作交给人工智能处理仍然完全合理,审查人员只需要掌握足够的信息来验证输出结果,而无需将其提升到共同理解的层面。何时掌握复杂核心部分背后的理论,何时又该放手,是你要做出的决策。

任何系统都必须经过理解、设计、构建、验证和维护这样一个流程。即使随着技术的发展,其中的某些环节会被加速。团队应该首先验证需求,确保其表述明确且可测试,并制定书面验收标准,然后才让开发人员开始编写代码。这并非要求进行大规模的前期设计,因为这里的工作单元是用户故事,而非整个系统。在智能体工程中,“决策-执行”循环运行得更快,人类工程师必须把握变更的意图——即“什么在改变”以及“行为在边界处如何变化”——这样才能在委托构建任务的同时不丢失理论基础。

让工程师用自己的语言解释代码,并将这一解释融入拉取请求(PR)描述和合并提交信息中。其价值完全在于手动编写提交信息/ PR 描述,而非由智能体自动生成。撰写信息这一过程本身,正是检验作者是否真正掌握理论的试金石。

持续维护共享模型

虽然个人理解是必要的,但仅靠个人理解仍然不够。当工程师脑海中有系统的简化模型时,理解上的偏差就很容易纠正。但当子系统之间相互通信,不同团队之间需要协作时,心理模型的偏差就会带来高昂的代价。有界上下文是必须建立共同理解的最重要的领域。例如,API 模式说明了请求和响应的结构、先决条件和授权,但并未涉及契约的行为方面——诸如幂等性、重试安全性、排序要求、一致性以及交付保证等。这些行为属于理论范畴,必须作为双方的共同理解而存在。ADR 和上下文图等工具与工件固然必要,但仅凭它们无法维持这种理解。只有通过促进知识流动,才能实现这种共享模型:维持经过深思熟虑的团队拓扑结构,让工程师跨模块结对编程并轮换,从而避免理论知识孤立地存在在于某一位工程师手中,与此同时,还要采用去中心化的架构决策机制。

图 2:个人的理解是必要的,但还不够——它必须传递给团队,才能经受住人员流动的考验(图片由作者使用 draw.io 制作)

随着生成式人工智能(GenAI)的出现,知识孤岛问题被进一步放大。当智能体生成代码的速度超过人类的理解能力时,知识流便在智能体与个人之间多出一个“第一跳”。给智能体发出指令的人必须能够解释系统的设计——不是通过从生成的输出中学习,而是通过将实际实现的内容与设计时脑海中的心理模型进行对照。可理解性检查点负责处理智能体到个人这一环节;上述实践则维持着理论知识从个人到团队的流动,从而使共同理解得以持久有效。如果可理解性是一种架构特性,那么理解债务(系统实际状态与团队对其理解之间的差距)就是一项会产生利息的负债。就像技术债务一样,它无法通过一次努力就能清偿。这些实践只有成为持续的行为习惯才能发挥作用,因此,必须将理解融入流程之中,使其成为针对尚未加入团队的工程师而设计的机制。

有意识地构建可理解性

将可理解性视为一种架构特性,意味着要像对待其他特性一样对待它:需要关注的首要指标、用于自动化的适应度函数,以及确保不偏离标准的实践。如果直到智能体生成的代码投入生产后才发现理解上的缺口,那么你就已经错失了低成本的解决方案。碎片化、代码更迭和生成式人工智能都会以各自的速度扩大这一缺口,而且它们都不会主动发出预警——系统仍然在通过各个关卡,而其背后的理论基础却在悄无声息地削弱。因此,在系统的接缝处、核心领域以及系统应当具备可演进性的任何地方,都要有意识地构建可理解性。

演进式架构承诺构建能够安全吸收变更的系统。这一承诺的价值取决于其底层的共同理解,而这种共同理解只有通过有意识且持续的构建才能得以维系。

原文链接:https://www.infoq.com/articles/system-comprehension-evolutionary-architecture/