跳转到内容

为什么好的软件也是 AI 友好的软件

几年前,我面对过一个与 AI 完全无关的架构问题。

一个产品必须支持多租户和白标。最终,大约需要交付七个客户端。它们在业务和技术上高度相关,同时又有刻意保留的差异。最简单的描述是:

same, but different.

当时,我已经对如何模块化 Angular 应用有了相当清晰的想法。缺少的是一种可持续的结构:既能支撑多个应用、共享能力和有意识隔离的差异,又不会最终演变成七份几乎彼此独立的复制品,或者一个几乎没人还能理解的配置怪物。

在寻找这种结构的过程中,我多少有些偶然地遇到了 Nx。并不是因为我一定想要一个 Monorepo,更不是因为我想为 Coding Agent 优化一个 Repository。今天这种形态的 Coding Agent 当时甚至还不存在。

我最初关心的是一个非常具体的产品问题。之后我才逐渐发现,共享 Workspace 还带来了更一般性的特性:应用、Libraries 以及它们之间的关系可以在同一个地方被发现;Tooling 可以统一;Dependencies 更容易看见;共同的技术能力可以被有意识地复用。

直到今天,对我来说重点仍然是 有意识地

我并不认同这样一种想法:Monorepo 主要就是为了最大化复用。共享代码库确实让复用更容易,但这并没有回答一个更重要的问题:再增加一个共享 Dependency,在架构上究竟是否合理。

回头看,我今天其实对另一件事更感兴趣:

许多今天对 Coding Agent 变得有价值的特性,最初其实是我们为人类构建的。

Monorepo 只是其中一个例子。可见性、共享 Toolchain、可复现的 Build、清晰定义的 Dependencies 和自动化测试,最初都是典型的 Developer Experience 主题。Coding Agent 只是让这些能力多了第二类使用者。

但这并不意味着,每一个大型共享 Repository 都会自动变得对 Agent 友好。

所有东西放在同一个地方,还不等于架构。

从 Repository 到可执行的工作空间

Section titled “从 Repository 到可执行的工作空间”

对我来说,更有意思的演变发生在后来。

在工作环境中,我长期习惯一种非常传统的分离方式:Frontend 在这里,Backend 在那里,各自拥有独立的 Repository、Build 流程和开发环境。两部分通过定义好的 Contracts 进行通信。

这本身完全合理。我可以在本地测试 Frontend,也可以在本地测试 Backend,还可以查看 Contract,理解双方应该如何交互。

但一旦错误跨越完整的系统 Flow,就会出现额外的边界。也许问题始于数据持久化,在某个 Service 中被错误转换,通过 API 继续传递,最后只在 Client 里显现出来。从技术上看,这其实是同一个错误场景。但在分析时,相关部分却分布在多个不同的工作空间中。

后来,Nx 对我来说有一件事开始比 Repository 组织本身更重要。Nx 通过 Project Graph 了解 Projects 及其关系。Targets 或 Tasks 描述这些 Projects 可执行的能力。Task Pipelines 和 dependsOn 可以表达 Tasks 之间的依赖,而 run-many 可以跨多个 Projects 执行同样的标准化 Tasks。

对我来说,关键思想并不依赖某一个具体的 Nx 版本:

如果一个 Workspace 知道自己的 Dependencies,它就不只是能组织文件。它还可以编排系统的一部分。

最初,这依然只是经典的 Developer Experience:更少的手工启动顺序,更少关于“哪个命令要在哪个项目里执行”的项目特定知识,也更少为那些 Workspace 本身已经知道的关系手写 Orchestration Logic。

后来,我在自己的 SCS 系统中更彻底地实践了这个想法。多个业务系统的核心部分位于一个共享代码库中。根据领域不同,其中包括 Client、API、Service、数据库相关基础设施、Libraries、Contracts、Container 配置、Tests,以及 Infrastructure as Code。

CI 也会利用这些系统知识。它通过 Nx 找出受影响的可部署 Projects,只构建相关的 Docker Targets,然后再将它们映射到具体的 Stacks 和 Services。

这会改变 Repository 本身的意义。它不再只是保存系统代码,而是越来越多地包含用于构建、启动、检查和运行系统关键部分的机制。

系统本身开始变成一个可执行的工作空间。

这项演变最初与 Agentic Work 完全无关。也正因为如此,我今天才觉得它格外有意思。

一个可执行的工作空间明确不意味着,我会给 Coding Agent 一个从数据库一路贯穿到 View、横切整个系统的单一任务。

如果 Requirements 足够清晰,Agent 也许确实能完成这样的端到端修改。但我的做法反而更接近相反方向:真正的工作包应该尽可能小。

共享系统真正改变最大的,是 分析

如果一个错误必须从 View 追踪到 Backend Service,甚至一直追到数据持久化层,我希望 Agent 的视野中尽可能少有人为边界。如果某个 Contract 被修改,依赖它的代码就应该可达。如果 E2E Test 失败,我不希望分析仅仅因为下一层相关代码碰巧位于另一个当前工作空间无法访问的 Repository 中就被迫结束。

而在实现阶段,我会采用另一条规则。

视野要宽,修改范围要窄:在分析阶段,Agent 应该能够跨系统理解相关 Flow;真正的实现则应被限制在一个小而自治的工作空间内。

对我来说,这并不矛盾。

Agent 可以进行系统级理解,而不必进行系统级修改。

本系列第 10 篇文章讨论了开放决策空间:什么时候需要探索,什么时候必须做决定,以及什么时候实现应该尽可能“无聊”。这里讨论的是一个相关但不同的问题。一个小的实现任务,并不意味着我们必须人为切断 Agent 对周围系统的一切视野。尤其是在分析和验证时,更大的可见范围可能非常有价值。

与此同时,一个巨大的实现任务也不会因为 Agent 理论上可以访问整个系统而自动变得更好。

架构应该同时支持两件事:宽广的视野和狭窄的修改范围。

但更宽的视野并不能解决另一个问题:Repository 从来不会自动包含团队对系统拥有的全部知识。

人类对这一点的补偿能力惊人。一个开发者也许在系统里工作了五年,他知道某个 Class 虽然看起来像正确入口,但新的功能不应该再从那里开始;他知道某个 Service 历史上放错了位置;他知道一条在代码里几乎看不见的 Dependency;他也知道两个现有 Pattern 中,哪一个虽然更常见,却早就不应该继续使用。

有时最诚实的描述只有一句:

这件事哪里都没写。我们就是知道。

软件开发是一项高度依赖知识的活动。关于 Software Maintenance、Tacit Knowledge 和 Architecture Decision Knowledge 的研究早已指出,领域知识、架构知识以及历史决策经常只被不完整地记录下来。Coding Agent 并不会改变这个基本问题。

它们只是不会自动拥有那些经过多年累积、存在于团队成员脑中的组织特定历史。

它们最初能够使用的,是那些可访问且可解释的东西:Code、Repository 结构、文档、Contracts、Tests、Build 配置、Agent Files、静态规则、版本历史、Logs 以及可执行 Tools。

当前关于 Coding Agent 的研究已经表明,即便只是找到正确的 Repository Context,本身也是一个独立的问题。Agent Retrieval Bench 正是把这个阶段单独拿出来研究,并显示在相当一部分记录到的 Agent Trajectories 中,所有被标注为相关的文件都可能被错过。ContextBench 从另一个角度研究同一机制,并发现“被探索到的 Context”和“真正被使用的 Context”之间存在可测量的差距。

这并不意味着每一条经验知识都应该被写进文档。那既不现实,也未必有意义。但它意味着:越多重要规则和关系可以直接从系统本身推导出来,系统对 Agent 来说就越不神秘。

Hidden Knowledge 与显式系统知识:只存在于团队成员脑中的知识,并不会自动对 Agent 可用;Contracts、Tests、Boundaries 和被记录的决策则可以访问,并且其中一部分可以被机械验证。

显式 Contracts 能帮助新开发者,也能帮助 Agent。架构规则能帮助团队,也能帮助 Agent。可复现的 Build 能帮助周一早上刚换新 Laptop 的开发者,也能帮助一个需要验证自己修改结果的 Agent。

就在这里,旧的软件工程思想得到了新的使用者。

我对一致性有一种相当明显的偏好。说得不那么外交一点:

一致性是我的信条。

当我划分业务 Domain 时,我希望它们在结构上读起来相似。不是完全相同——不同的业务问题可能需要不同的解决方案。但熟悉的 Layer 应该具有相同含义,重复出现的 Pattern 应该可识别,Commands 不应无缘无故不同,Lint Rules 应该集中生效,而 Tooling 配置应尽可能少制造局部惊喜。

早在我认真使用 Coding Agent 之前,这就是我作为 Tech Lead 时的基本态度。

有意思的是,我现在反而更谨慎,不会轻易说 Agent 需要 与人类一样程度的一致性。人类很依赖识别模式来建立方向感。如果十个业务区域结构相似,第十一个区域的认知进入成本就会降低。Agent 则可能完全可以分析其中一个区域,而从未看过其他九个。

而这其实甚至是我们希望看到的结果。

如果架构让 Agent 根本不需要理解整个 Repository,那么它就没有必要理解整个 Repository。

一致性依然有价值。它稳定了相关区域内的预期,使通用检查成为可能,减少 Build 和 Test Logic 中的特殊知识,也提高显式规则真正能够跨系统执行的概率。

这也引出我的第二个信条:

Stick to the standards.

我自己在 Nx 上已经亲身经历过:如果结构无谓地偏离工具预期的机制,会发生什么。自定义 Executor Logic、特殊配置和个性化 Project 结构,在短期看起来可能很聪明。但到后续升级时,这些特殊知识也必须一起迁移。

这并不是说所有 Default 都应该不加思考地接受。Framework 和 Tooling Standard 不是自然法则。更有意思的 Engineering Rule 是:

每一个没有必要的偏离,都会制造额外的特殊知识。

对人类如此,对 Tooling 如此,而且越来越多地,对 Agent 也如此。

所以这里可以允许我稍微为 Nx 说几句好话。但不是要把每一个软件系统都做成 Nx Monorepo,更不是说 Nx 是一个 AI Platform。

我真正喜欢 Nx 的地方,是它非常具体地展示了:经典的软件工程思想如何变得 显式、可查询、可执行。Workspace 由 Projects 构成;Project Graph 描述它们之间的关系;Tags 可以表达语义类别;Targets 或 Tasks 描述可执行能力;Task Pipelines 描述这些 Tasks 之间的 Dependencies;run-many 可以跨多个 Projects 执行标准化 Tasks;affected 则把 Git Diff 与 Project Graph 结合起来,计算出 Workspace 中可能被影响的部分。

Graph 本身也不只是一个可视化工具。Nx 可以导出 Project Graph 和 Task Graph 数据,或者在具体执行中把它们暴露出来。于是 Repository Topology 的一部分就变得机器可读。

我喜欢 Nx,并不是因为每个系统都应该是 Monorepo。我喜欢的是:Repository Topology 的重要部分可以变得显式、可查询、可验证。

对人类来说,这是良好的 Developer Experience。对 Agent 来说,它变成了可访问的结构。

我几乎从开始使用 Nx 的第一天起,就一直在使用 Module Boundaries。这里同样与 AI 的最初动机无关。

例如,我希望防止 UI-specific Libraries 突然被用到 Business Layer。不同业务 Domain 不应该任意导入彼此的内部实现。Dependency Direction 不应该只存在于一张 Diagram 里。

对于 JavaScript 和 TypeScript Projects,Nx 提供 @nx/enforce-module-boundaries。Projects 可以用 Tags 分类,而 Dependency Constraints 可以定义某个区域允许访问哪些类别,或者明确禁止哪些 Dependencies。对于跨语言、基于完整 Project Graph 的规则,Nx 还提供了 Conformance Rules。

第 8 篇文章讨论的是,为什么这些机制能限制结构上允许的解决空间。这里我更感兴趣的是另一个效果:它们会提供反馈。

Agent 修改了 Code。然后 Lint 运行。架构就可以对一次具体违规直接作出回应。

不允许这个 Import。

此时,Agent 并不需要完美理解我的架构意图。它会收到关于自己修改结果的机器反馈。

机械可执行的架构,可以直接告诉 Agent:它自己的修改在哪个地方越过了边界。

这就是“只存在于 Wiki 页面中的规则”和“违规时直接产生失败 Task 的规则”之间的区别。文档解释意图,可执行规则检查结果的一部分。两者都有自己的位置。

Nx 中我非常喜欢的一个概念就是 affected

它的核心行为很直接:Nx 通过 Git 找出发生变化的 Files,通过 Project Graph 把它们映射到 Projects,然后再把依赖这些 Projects 的项目考虑进来。因此,Test、Lint 或 Build 等 Tasks 可以被限制在这一子集上。

也正是在这里,affected 对我来说开始有了架构意义。

如果所有东西都依赖所有东西,那么所有东西也会一直处于 affected 状态。形式上 Project Graph 依然存在,但它作为 Scope Limiter 的实际价值会显著下降。

要让 affected 真正强大,系统需要一些本来就值得追求的属性:Autonomy、干净的 Dependencies、有限的 Change Radius,以及有意识设计的 Boundaries。

在我的私有系统中,affected 已经不再只是 CI Optimization。Pipeline 通过一个 docker:build Target 找出受影响的 Projects,然后把 Build 和 Deployment 限制在由此得到的 Services 或 Stacks 上。

对 Agentic Work 来说,这又多出了一层解释。

Affected 作为验证 Scope:在 Dependencies 被干净切分的情况下,一个小修改只会产生一个较小的受影响 Graph,从而缩小需要验证的空间。

这明确不是一个通用的架构指标。一个核心 Library 完全可能合理地拥有很多 Consumers。有些修改本来就是系统级的。而一个错误的 Dependency Graph 也可能只是制造虚假的安全感。

不过作为 Engineering Signal,我仍然觉得这个 Scope 很有意思。

一个较小的 affected Scope 不只是更快。它也可能意味着,这个修改可以在局部被理解并在局部被验证。

因此对我来说,affected 同时变成了 CI Optimization、Scope Detector 和 Verification Aid。

复用,但不要变成 Shared-Code Festival

Section titled “复用,但不要变成 Shared-Code Festival”

走到这里,很容易出现一种经典的 Monorepo 反射:既然所有东西都可以彼此访问,那我们终于可以把所有东西都复用了。

从技术上讲,这没错。从架构上讲,我认为这个结论很危险。

对于 业务复用,我会非常仔细地判断。如果两个区域真正拥有同一个业务概念、相同的 Responsibility、相同的 Change Reason,那么一个共同 Owner 可能是合理的。

对于纯技术复用,我要谨慎得多。一个 OAuth Guard 如果确实代表一项共同的技术 Capability,那么它可以值得拥有自己的 Library。但两个只是碰巧看起来几乎一样的 Mapper,并不足以让我把原本自治的两个区域耦合起来。

我宁可把一个 Mapper 写两次,也不愿意为了省三行代码,在两个自治区域之间引入一个新的 Dependency。

这不是反对复用的绝对规则,而是把 Autonomy 放在反射式 DRY 之前。

Vaughn Vernon 在这个语境下提醒过,DRY 很容易被误解为单纯避免相同代码行,而其底层思想更接近于避免重复知识。对我的论证来说,更谨慎的结论已经足够:看起来相似的 Code,并不自动意味着它们代表同一个 Model,也不自动构成引入新 Dependency 的理由。

Monorepo 不是一场 Shared-Code Festival。共享代码库让复用成为可能,但什么时候它应该成为真正有意义的 Shared Capability,什么时候它只是引入新的 Coupling,仍然要由架构决定。

从 Developer Experience 到 Agent Infrastructure

Section titled “从 Developer Experience 到 Agent Infrastructure”

写到这里,可能会让人产生一种印象:这篇文章主要是在讲 Monorepo 和 Nx。那就把重点看窄了。Nx 只是一个非常具体、很好理解的例子。

真正的思想,在我们把视野扩大到整个开发环境时才会完整显现。

多年来,我们一直在为可复现的本地环境、Containers、统一启动命令、Unit Tests、Integration Tests、E2E、Lint、Formatting、Type Checks、Build Targets、Dependency Graphs、Architecture Rules、Infrastructure as Code 和 CI Pipelines 投入大量工作。

最初的使用者是开发者。

良好的本地环境,是为了避免新团队成员花三天去解释一份手写 Setup 指南;标准化 Build 应该可复现;Tests 应该提供反馈;Lint 应自动识别低级错误和规则违规;Containers 应减少环境差异;CI 应可靠重复相关检查。

这些东西原本都属于 Developer Experience。

后来,这套基础设施多了一个新的使用者。

Coding Agent 今天可以自己使用同样的机制。

Developer Experience 变成 Agent Infrastructure:Agent 完成修改,调用 Build、Lint 和 Tests,启动相关 Services,执行 E2E,读取错误,进行修正,然后再次验证。

这不仅仅是一个理论上的产品设想。SWE-agent、SWE-Gym 和 SWE-bench 这类 Software Engineering Benchmarks 与 Agent Environments,正是建立在这样一种能力之上:导航 Repository、编辑 Files,并通过可执行的 Runtime 和 Test Environment 检查生成的修改。

这并不能推导出关于我自己 Repository 的普遍生产力结论。但底层机制依然很清晰:当 Coding Agent 不只是生成 Text,而可以与一个可执行的软件环境进行交互时,它的实用性会显著提高。

过去属于 Developer Experience 的东西,正在越来越多地变成 Agent Infrastructure。

对我来说,这可能是整个论证中最重要的结论。

当我们讨论 AI 友好的 Repository 时,很快就会谈到 Context:好的文档、Agent Files、清晰文件名、Repository Maps、Architecture Overview。这些都很重要。Agent 必须能够找到相关信息。

但一个 Repository 即使非常容易阅读,也仍然可能是一个糟糕的 Agentic Workspace。

假设一个系统拥有优秀文档。Agent 理解了架构,找到了正确的 Service,识别了相关 Contract,并写出了一个看起来很合理的 Patch。

然后呢?

Build 只在某个同事的电脑上能跑。Tests 基本不存在。Backend Stack 已经几个月无法在本地启动。所谓 E2E,就是有人在测试环境中手动点五下。不同子项目的 Lint Rules 各不相同。最重要的一条架构规则只存在于一张 Diagram 中。

Agent 可以很好地描述自己的假设,但几乎无法验证它。

因此,AI 友好并不只是 agent-readable。它还意味着 agent-verifiable。

或者更一般地说:

AI 友好的软件,不只是给 Agent Context。它还给 Agent Feedback。

更大的 Context Window 能装下更多 Files,却不能创造一个能运行的 Test。更好的 Repository Search 能找到正确的 Service,却不能把坏掉的本地 Build 变得可复现。一份优秀的 Agent File 可以解释 Presentation 不应依赖 Infrastructure,而一条机械执行的 Boundary Rule 则可以进一步直接拒绝这次具体违规。

于是,我们判断一个优秀 Agentic Workspace 的方式也发生了变化。不只是:Agent 能看见多少 Repository Context? 还包括:它能多可靠地从“我觉得这个修改是对的”走到一个可信的验证结果?

在我自己的工作中,我确实会让 Coding Agent 使用这些机制。

修改完成后,我不希望 Agent 只展示一个 Diff。根据任务不同,它会执行 Unit Tests、运行 Lint、Build 相关 Projects、在合适的 Feature 上启动必要的 Stack、执行 E2E、读取错误、修正实现,然后再次验证。

这些步骤没有任何一个能够证明完整的业务正确性。一个绿色 Build 并不能说明我们做的是正确的 Feature。一个 Unit Test 可以完美地保护一个错误假设。一个 E2E Test 只验证我们真正描述过的场景。Module Boundaries 也无法阻止每一种糟糕的架构决定。

真正重要的是不同 Feedback Channel 的组合。Build 回答的问题不同于 Lint;Architecture Rules 回答的问题不同于 Unit Tests;E2E 回答的问题不同于 Type Checks;Human Review 回答的问题也不同于所有自动化机制。

对我来说,当 Agent 能在结果交到我手里之前,自己尽可能使用这些 Channel 时,Agentic Work 会变得更加稳健。

因此,我自己的 Review 重点也会发生移动。如果 Agent 已经自己 Build、Lint 和 Test 过,我就可以更集中地关注这些问题:Responsibility 是否位于正确的位置?是否产生了新的不必要 Coupling?业务 Boundary 是否仍然保持?解决方案是否符合之前已经做出的架构决定?Patch 是否带来了结构 Drift?这个方案真正合适,还是只是技术上能运行?

Agent 越来越多地自行验证功能和技术一致性,而我自己的 Review 则更强地转向架构和决策。

这里的“更强地”很重要。我仍然会 Review 功能,不会盲信一次绿色 Test Run,也不会把架构交给 Review Model。第 10 篇文章已经解释过,为什么我还会经常使用一个强模型作为额外 Review Channel。对我来说,Human Review 和 Model Review 互相不能替代,而可执行基础设施则补充了两者。

目前我也并不主要优化最短运行时间。如果必须在“一个非常快的绿色信号”和“一个完整、可追溯的报告”之间选择,我会选择报告。

我关心的不只是 Tests 是否为绿色,还包括:哪些 Projects 被影响了、Build 了什么、运行了哪些 Tests、检查了哪些 Architecture Rules、哪些内容被跳过、出现了什么 Warning,以及在最终成功之前是否曾经先修复过某些 Failure。

这个结果随后还会承担第二种功能:它本身又成为 Context。我可以检查这个 Report,也可以把它交给额外的 Review Model,例如询问:这次修改所做的验证是否足够,或者还有哪些风险没有覆盖。

快速 Feedback 很舒服。可信的 Feedback 才是关键。

干净的 Boundaries 和 affected 本来就会缩小 Scope。Parallelization 或 Subagents 可以继续优化运行时间,但也会带来其他成本问题。这属于后面的文章。

如果必须把前面的论证压缩成几个属性,我最终会落到三个词上。不是作为又一份“AI-ready Repository Checklist”,而是描述我自己的实践中哪些东西运行得很好。

显式: 重要结构应尽可能在系统里可见。Projects、Dependencies、Contracts、Layers、Commands、Tests、Architecture Rules,以及那些未来无法可靠从 Code 里重新推导出理由的决策,都应该适当地被记录。一个 Agent 不应该为了普通修改先猜测团队多年的历史。

受限: 可以拥有系统级可达性,但具体任务仍应拥有一个小型 Workspace。某个业务 Domain 不需要仅仅因为另外五个 Domain 恰好位于同一个 Repository 中,就必须知道它们的信息。

可验证: Agent 需要通过 Build、Lint、Tests、Architecture Rules、可复现 Runtime,以及在合适场景中的 E2E 获得可信 Feedback。

由此,我得到一个相当精确的描述:

AI 友好的软件会让相关知识显式化,把具体工作空间保持得足够小,并让 Agent 能验证自己的修改。

有意思的是,这三个属性没有一个是为 AI 发明的。

Monorepo 是基础设施,不是 Agent 策略

Section titled “Monorepo 是基础设施,不是 Agent 策略”

这让我们回到文章开头的地方。

一个拥有两百万行 Big Ball of Mud 的 Repository,不会因为所有 Files 恰好位于同一个 Git Checkout 中,就自动变得 Agent-friendly。Agent 也许现在可以访问所有东西,但这仍然没有告诉我们:它为了某个具体修改究竟必须理解多少东西。

大型结构化 Repository 与巨大解释空间的对比:清晰的业务 Boundaries、显式 Dependencies 和小型局部 Workspace,可以在系统级可达的同时保持有限 Scope;而全局 Utilities、模糊 Ownership、Cross-Dependencies 和互相竞争的 Patterns 会制造巨大的解释空间。

另一个同样大小的 Repository,可以拥有完全不同的结构。关键不是 Checkout 有多大,而是为一个具体修改真正需要理解多少内容。

所以我绝不会写“Monorepo 会自动让 Agent 变得更好”。这个结论既没有清晰因果关系,也不是一种通用架构规则。

我的主张要小得多:

Monorepo 是基础设施。Agent 友好性来自结构。

共享 Workspace 可以让系统级分析和 Orchestration 更容易。它最终能否成为一个好的 Agentic Workspace,取决于它自身的结构。

这就带来一个不太舒服的反问题。

在这个网站上,我已经从长期架构和经济角度详细讨论过 Big Ball of Mud。Coding Agent 也许会改变其中一些事情,只是改变的方向可能和我们最初想象的不一样。

我目前的个人假设是:Coding Agent 也许能够在一个 Big Ball of Mud 中保持生产力非常久。甚至可能比人类更久。

Agent 可以搜索大量 Code,追踪重复结构,重建很深的 Call Chains,并一次又一次重新分析同样的关系。人类在这类系统中最终会遇到认知和组织边界,并不意味着 Agent 会在同一个位置失败。

当前 Repository Retrieval 研究确实清楚地表明,大型 Codebase 和 Context Acquisition 仍然是困难问题。但这些研究并没有证明,Agent 在无结构系统中就一定会立刻失败。

因此,对我来说下面这个说法太方便了:

糟糕的架构无法与 AI 一起工作。

它很可能能工作相当长时间。

真正更有意思的问题是:代价是什么?

从这里开始,我们就离开可靠的经验结论,进入 Engineering Hypothesis。一个持续增长的 Big Ball of Mud 会扩大潜在相关信息的空间。需要调查更多 Dependencies,需要比较更多历史 Patterns,需要排除更多 Ripple Effects,也需要 Review 更多结果。

长期来看,这可能提高 Context Acquisition、Model Context、分析成本、Verification Scope、Review 成本以及每次修改的变量成本。我并不知道有哪项可靠研究能够让我们今天画出一条通用的经济曲线,所以我也不会编造一条。

但作为 Engineering Inference,我仍然认为下面这个结论是合理的:

Agent 在技术上能够处理一个 Big Ball of Mud,并不能回答另一个经济和人类层面的问题:继续演化它是否仍然合理。

说得更尖锐一点:

一个 Big Ball of Mud 也许能很长时间都让 Agent 无所谓。但预算,以及仍然需要理解生成解决方案的人类,未必也无所谓。

AI 甚至可能推迟我们把一个问题系统视为“经济上不再可维护”的那个临界点。今天,人类经常能把这类系统维持很久,因为他们逐渐积累 Hidden Knowledge。Coding Agent 可能会把其中一部分导航过程自动化。

与此同时,也存在相反的动力:如果 Agent 能更快地产生修改,它们也可能更快地放大结构问题。更多 Output 并不自动意味着更多 Architecture。如果每一次快速修改都添加一个新的局部例外,那么更高生产力也可能加速结构侵蚀。

我们今天还不知道,长期来看哪一种力量会占上风。

Agentic Work 不会消除 Big Ball of Mud 的技术属性。它可能改变的是,我们愿意容忍这些属性带来的经济后果多久。

完整的经济计算属于后面的经济篇。

对于新功能,同样的成本变化也可能向相反方向发挥作用。

很多糟糕的架构决定,并不是因为没人能看到更好的结构,而是因为短期更便宜的路径很诱人:在现有大组件里再塞一个 Feature,在现有 Service 里再加一个特殊情况,让同一个 Shared Library 再多一个 Consumer,再引入一个小 Dependency。

一个干净的新 Boundary 本身同样需要时间。

如果 Agent 让 Code Production 变得更便宜,这笔账就可能发生变化。相比把下一个 Feature 继续塞进一个已经过载的组件,也许创建一个新的、小型自治单元会变得更经济。

这不是在鼓吹 Microservices。一个自治单元同样可以只是一个业务 Slice,或者 Modular Monolith 内部的一个 Module。

真正有意思的思想只是:

更便宜的 Code Production,也可能改变创建良好边界的成本。

这个问题会直接引向本系列后面的一个主题:当 Code 本身不再是软件开发中最昂贵的部分时,Software Architecture 会发生什么?

当年我用 Nx 支撑七个“相似但又不同”的 Clients 时,我不是在构建 AI-friendly Repository。我们统一 Build Commands 时,目标是 Developer Experience。Module Boundaries 是为了防止不需要的 Dependencies。自动化 Tests 是为了让人类更快得到 Feedback。Containers 是为了让本地环境更可复现。Infrastructure as Code 是为了让基础设施更容易理解和复现。affected 最初只是为了让 CI 更有针对性。

今天,这些东西都多了一个新的使用者:Coding Agent。

它可以读取 Contract,分析 Project Graph,调用标准化 Target,把 Boundary Violation 作为 Lint Error 收回来,执行 Test,启动 Container,读取 Failure,修正自己的 Patch,然后再次执行这个循环。

这并不会突然改变“好软件”的定义。但它改变了 谁能够从这些属性中受益

因此,一个优秀工作空间的质量,并不只取决于 Context Window 有多大,或者下一代模型有多聪明。其中一部分仍然非常传统地存在于我们的 Engineering 里:存在于 Boundaries、Contracts、Toolchain、Tests、Reproducibility,以及“到底有多少相关知识真正能在系统里被看见”这个问题里。

AI 友好软件的许多属性,其实只是旧的软件工程思想有了新的使用者。

因此,这个视角变化对我来说已经非常清晰:

过去属于 Developer Experience 的东西,正在越来越多地变成 Agent Infrastructure。

也许 AI Readiness 因此并不是一种完全新的质量维度,而更像是一种额外测试:我们软件的质量究竟真正存在于系统本身,还是主要存在于那些多年下来已经学会如何与其缺陷共处的人脑中。

AI 友好的软件会让相关知识显式化,把工作空间保持得足够小,并让 Agent 有能力验证自己的修改。

  • Nx 文档: 对 Project Graph、Task Graph、Targets、Task Pipelines、dependsOnrun-manyaffected 以及 Module Boundaries 的描述,均以 2026 年 9 月的状态为准。直接参考:Project Graph 与 Task GraphTargets 与 run-manyTask Pipelines 与 dependsOnaffected 以及 Module Boundaries。对于 JavaScript / TypeScript Projects,Nx 当前使用 @nx/enforce-module-boundaries;针对完整 Project Graph 的跨语言规则则存在 Conformance Rules。
  • Monorepos: 本文的判断部分参考了大型共享代码库方面的成熟研究,其中包括 Google 生态中的相关工作。Code Visibility、集中式 Toolchain 和共同修改等优势,与真实存在的 Trade-offs 并存。这明确不意味着 Monorepo 对每一个系统来说都是更好的 Repository Strategy。
  • Agent Retrieval Bench 与 ContextBench: 两项工作从不同角度研究 Coding Agent 中的 Context Acquisition。它们支持这样一个判断:找到并真正使用相关 Repository Context 本身就是一项独立任务;它们并没有证明某种具体架构会自动节省某个确定数量的 Tokens。
  • SWE-agent、SWE-Gym 与 SWE-bench: 这些工作或 Benchmarks 为 Software Engineering Agents 使用可执行的 Repository、Runtime 和 Test Environment。它们支持一个基本机制:拥有 Tool Access 和可执行 Feedback 的 Agent,能够做的事情比纯 Text Generation 更多。本文并未由此推导任何普遍生产力结论。
  • Tacit 与 Architectural Knowledge: 对 Hidden / Tacit Knowledge 的讨论基于 Peter Naur 关于程序理解与演进的 Programming as Theory Building(1985),以及 Tang 等人关于决策知识使用与记录的 A survey of architecture design rationale(2006)。这里指的是不会自动从 Repository 中显现出来的组织特定知识,而不是声称模型完全没有任何先验知识。
  • Big Ball of Mud 与经济性: 关于长期 Context、Review、修改成本,以及 AI 是否可能让问题系统在经济上维持更久的判断,都明确属于 Engineering Hypotheses。目前并没有足够的经验数据支持一条可靠、通用的经济曲线。