架构漂移检测:保持代码与设计的一致性 - Archyl Blog

架构漂移是文档化设计与实际代码之间的无声偏离。本指南解释了漂移的成因、如何检测它,以及如何防止你的架构图沦为一纸空文。

架构漂移检测:保持代码与设计的一致性

在你的组织中,某处存在着一张错误的架构图。也许它展示了一个六个月前已被合并到其他服务中的微服务。也许它标注着 Redis 作为缓存层,而团队早在一次生产事故中就切换到了 Memcached。也许它描绘了一个整洁的六边形架构,而这个服务实际上已经积累了足够多的捷径和变通方案,看起来更像一团意大利面。

这就是架构漂移:系统文档化描述与实际运行方式之间逐渐的、无声的偏离。与 Bug 不同,漂移不会触发告警。与性能退化不同,它不会出现在监控中。它静静地潜伏着,直到有人基于过时的文档做出了决策——而那个决策最终被证明是错误的。

架构漂移是普遍存在的。每个团队都会经历它。问题不在于你的文档是否会漂移,而在于你多快能发现它,以及你将如何应对。

什么是架构漂移?

架构漂移是指软件系统的实际实现偏离其文档化或预期架构的现象。这一术语诞生于学术软件工程界,但对于每一位实践中的工程师来说都再熟悉不过了。

漂移在架构文档的各个层面都会出现:

结构漂移

文档化的结构不再与代码库匹配:

  • 一个记录为独立容器的服务被吸收进了单体应用
  • 一个组件被重命名了,但图表仍然显示旧名称
  • 一个新服务被创建了,但从未被添加到架构模型中
  • 数据库从 MySQL 迁移到了 PostgreSQL,但容器图仍然标注为 MySQL

行为漂移

文档化的行为不再与现实匹配:

  • 一个同步 API 调用被替换为异步消息,但关系描述仍然标注为 "REST/HTTP"
  • 数据流被改为通过 API Gateway,但图表仍显示服务间的直接通信
  • 添加了一个认证步骤,但未反映在系统上下文图中

依赖漂移

文档化的依赖关系不再匹配实际的集成情况:

  • 一个第三方 API 被替换为内部自研方案
  • 添加了一个新的外部依赖(支付服务商、监控服务),但未被记录
  • 一个集成已被停用,但仍出现在系统上下文图中

决策漂移

文档化的架构决策不再被遵循:

  • 一条 ADR 规定"所有持久化存储使用 PostgreSQL",但某个团队开始使用 MongoDB
  • 合规性规则规定"前端不得直接访问数据库",但有人添加了客户端的 Supabase 集成
  • 部署架构标明"单区域部署",但服务实际上已部署到了多个区域

架构漂移发生的原因

理解漂移的成因对于预防至关重要。漂移通常并非出于恶意甚至疏忽——它是软件开发方式的自然产物。

速度优先于文档

当需要在周五之前交付功能时,更新架构图往往是第一个被舍弃的。代码变更才是可交付成果,文档更新只是额外开销。这在短期内是理性的行为,但从长远来看却是灾难性的。

众多微小变更

漂移很少发生在某个戏剧性的时刻。它通过数百个微小的变更逐渐累积,每一个都小到不值得更新文档:

  • 重命名一个文件
  • 添加一个工具包
  • 更换一个库依赖
  • 将一个函数提取到独立模块中

没有哪个单独的变更重要到需要触发文档更新。但它们加在一起,却改变了整个架构。

团队人员流动

当工程师离开时,他们带走了隐性知识。新团队继承了代码库,却没有继承对其结构设计原因的理解。他们基于代码中看到的内容而非文档所述来进行修改,进一步加剧了漂移。

缺乏反馈循环

如果没有人检查文档是否与现实匹配,漂移就是不可见的。没有检测机制的情况下,发现漂移的唯一途径就是在事故中、审计中,或者当新工程师指出图表与代码不一致时。届时漂移可能已经相当严重了。

紧急变更

生产事故常常需要架构上的捷径:绕过 API 层的直接数据库连接、硬编码的配置而非使用配置服务、本应临时的缓存变成了永久方案。这些变更绕过了正常的审查流程,且几乎不会被记录下来。

架构漂移的代价

漂移不仅仅是美观问题。它有着具体的、可衡量的代价。

错误决策

当架构师基于过时的文档做决策时,那些决策可能是错误的。"这个服务流量不大,所以我们可以使用同步依赖"——但文档已经过期了,该服务实际处理的负载是文档记录的 10 倍。

新人上手缓慢

新工程师依赖架构文档来建立心智模型。如果文档是错误的,他们就会建立错误的心智模型。他们编写的代码不符合实际架构。他们提出的问题暴露了他们的困惑,消耗了资深工程师的时间。

事故响应

在生产事故中,架构图应该帮助团队理解影响范围和依赖关系。如果这些图是错误的,团队会浪费宝贵的时间追踪错误的依赖链或遗漏关键的上游系统。

合规和审计失败

在受监管行业中,架构文档通常是合规所需的(SOC 2、ISO 27001、HIPAA)。如果审计人员发现文档与现实不符,这将构成一项发现——可能是严重的发现。

AI Agent 的混淆

随着 AI 编码 Agent 日益普及,它们越来越依赖架构文档来获取上下文。一个读取了过时 C4 模型的 Agent 会生成符合文档化架构而非实际架构的代码。这只会放大漂移,而不是修复它。

如何检测架构漂移

人工审查(传统方法)

最简单的方法是定期人工审查:召集团队,逐一检查架构图,确认它们是否仍然与现实匹配。

适用场景:小团队、简单架构、按季度进行。

失效场景:大型系统、快节奏团队,或者最了解代码的人没有时间参加评审会议。人工审查还容易受到确认偏差的影响——人们倾向于看到他们期望看到的东西。

架构适应度函数

适应度函数由 Neal Ford 和《Building Evolutionary Architectures》一书推广,是验证架构属性的自动化测试:

// 示例:确保 handler 包中没有直接的数据库导入
func TestNoDatabaseImportsInHandlers(t *testing.T) {
    packages := analyzeImports("./internal/handler/...")
    for _, pkg := range packages {
        for _, imp := range pkg.Imports {
            assert.NotContains(t, imp, "database/sql",
                "Handler %s imports database/sql directly", pkg.Name)
            assert.NotContains(t, imp, "gorm.io",
                "Handler %s imports GORM directly", pkg.Name)
        }
    }
}

适应度函数在执行特定规则方面非常强大,但它们需要前期投入来编写和维护。它们检查的是约束条件,而非完整模型。

静态分析工具

ArchUnit (Java)、Deptrac (PHP) 和 go-arch-lint (Go) 等工具可以分析代码结构并强制执行依赖规则:

// go-arch-lint 配置
components:
  handler:
    in: ./internal/handler/
  service:
    in: ./internal/service/
  repository:
    in: ./internal/repository/

rules:
  handler:
    can_depend_on: [service]
  service:
    can_depend_on: [repository]
  repository:
    can_depend_on: []

这些工具非常适合在单个代码库中强制执行分层架构。但它们无法解决跨服务漂移问题,也无法验证架构模型是否与代码匹配。

自动化漂移评分

这正是 Archyl 采用的方法。它不是检查特定规则,而是将整个架构模型与代码库进行验证:

  • 每个文档化的系统是否对应一个代码仓库?
  • 每个文档化的容器是否对应代码库中的一个目录?
  • 每个文档化的代码元素是否引用了一个仍然存在的文件?
  • 每个文档化关系的两端是否仍然有效?

结果是一个漂移评分(0-100)和详细的分解报告,精确显示哪里发生了漂移。这是最全面的方法,因为它验证的是完整模型,而不仅仅是特定约束。

Archyl 漂移检测的核心设计决策:

轻量级。 不消耗 AI token,不读取文件内容。仅通过 Git 服务商 API 进行文件路径存在性检查。这意味着漂移评分只需几秒钟,而非几分钟。

确定性。 相同的代码库、相同的模型,得到相同的评分。不会因 LLM 温度参数或 prompt 工程而产生变化。

低成本。 每次推送都可以运行,无需担心成本。一天计算一百次完全没问题。

可操作。 分解报告精确显示哪些元素发生了漂移,让你清楚知道该修复什么。

如何预防架构漂移

检测是必要的,但还不够。目标是从一开始就防止漂移积累。

将文档更新纳入完成定义

如果代码变更修改了架构,那么 PR 应该包含文档更新。在你的 PR 模板中添加一个复选框:

## 检查清单
- [ ] 测试通过
- [ ] 代码已审查
- [ ] 架构文档已更新(如适用)

这不能捕获所有情况,但它建立了文档是一等可交付成果的期望。

在 CI 中自动化漂移检测

最有效的预防机制是一个当漂移超过阈值时会失败的 CI 门控:

on:
  push:
    branches: [main]

jobs:
  drift:
    runs-on: ubuntu-latest
    steps:
      - uses: archyl-com/actions/drift-score@v1
        with:
          api-key: ${{ secrets.ARCHYL_API_KEY }}
          organization-id: ${{ secrets.ARCHYL_ORG_ID }}
          project-id: 'your-project-uuid'
          threshold: '70'

当构建因漂移评分下降而失败时,就必须有人在合并前修复它。文档准确性变得像通过测试一样不可妥协。

建议从较低的阈值(50-60%)开始,随着团队养成习惯逐步提高。

使用 Architecture-as-Code

当你的架构模型以文本格式定义(Structurizr DSL、Archyl YAML)时,它可以与代码一起进行版本控制。这意味着:

  • 架构变更出现在 Pull Request 中
  • 变更由团队审查
  • 架构演进的历史被记录在 Git 中

这远优于在 GUI 工具中定义的架构,后者的变更是不可见且无法审查的。

设置漂移告警

Archyl 支持漂移事件的 Webhook 告警:

  • drift.score_computed:每次漂移计算时触发。发布到 Slack 频道以提高可见性。
  • drift.score_degraded:当评分下降 10 分以上时触发。这是你的预警系统。

将这些告警配置到你的团队监控的频道。意识到问题是采取行动的第一步。

进行架构评审

每月或每季度的架构评审有多重目的:

  • 验证文档化的架构是否仍然与现实匹配
  • 识别自动化工具遗漏的漂移(例如行为漂移)
  • 讨论漂移的组件应该在代码中修复还是在文档中更新
  • 审查和更新可能需要重新考虑的 ADR

采用合规性规则

合规性规则定义了应该始终为真的架构约束:

  • "前端容器不得依赖数据库容器"
  • "所有公共 API 必须通过 API Gateway"
  • "每个服务必须拥有自己的数据库(不共享数据库)"

在 Archyl 中,合规性规则在平台中定义,并通过合规性检查功能强制执行。AI Agent 可以通过 MCP 读取这些规则,并在生成代码时遵守它们。

合规性规则与漂移检测是互补的。漂移检测检查你的模型是否与现实匹配。合规性检查检验现实是否遵循你的规则。

架构漂移 vs. 架构侵蚀

这两个术语相关但不同:

架构漂移是文档与实现之间的偏离。代码本身可能完全没问题——只是文档是错误的。

架构侵蚀是架构本身的退化。代码违反了架构原则,积累了技术债务,变得越来越难以维护。侵蚀是代码质量问题。漂移是文档准确性问题。

它们常常同时发生。当文档漂移时,团队对预期架构失去了认知。没有这种认知,他们就会做出侵蚀架构的变更。漂移助长了侵蚀。

这就是为什么漂移检测的意义超越了文档准确性本身。准确的文档作为参考,可以防止侵蚀。当每个人都能看到预期的架构时,他们更有可能去维护它。

随时间衡量和跟踪漂移

单次漂移评分有用。但趋势更有力量。

建立基线

运行你的第一次漂移计算以确定当前状态。如果评分较低不必恐慌——大多数未主动维护架构文档的团队会看到 40-70% 的评分。

设定目标

制定切合实际的改进目标:

  • 第 1 个月:通过修复最明显的漂移,从基线提升至 60%
  • 第 3 个月:通过将文档更新纳入工作流程,达到 75%
  • 第 6 个月:通过 CI 门控和定期评审,保持在 80% 以上

跟踪趋势

Archyl 存储每次漂移计算及其完整分解。漂移历史视图展示了评分的时间线,让你可以看到:

  • 漂移是随时间好转还是恶化?
  • 某个特定的迭代或发布是否导致了显著下降?
  • CI 阈值是否有效防止了退化?

庆祝进步

当团队提升了漂移评分时,给予肯定。架构文档是吃力不讨好的工作。让进步可见并得到认可,有助于强化这一行为。

漂移检测在 AI 辅助开发中的角色

AI 编码 Agent 的兴起使漂移检测比以往更加重要。

AI Agent 越来越依赖架构文档来获取上下文。通过 MCP 等协议,Agent 可以在生成代码之前读取你的 C4 模型、ADR 和合规性规则。这使它们更加高效——它们生成的代码符合你的架构,而不是凭猜测。

但这只有在文档准确的情况下才有效。一个读取了过时 C4 模型并基于它生成代码的 Agent,会产生符合错误架构的代码。Agent 放大了漂移而非阻止了它。

漂移检测创建了让 AI Agent 保持诚实的反馈循环:

  1. Agent 通过 MCP 读取架构
  2. Agent 生成代码,使其符合文档化的架构
  3. 代码被合并,可能改变了实际架构
  4. 漂移检测运行,捕获任何偏离
  5. CI 门控失败,如果漂移超过阈值
  6. 团队更新文档以反映现实
  7. Agent 读取更新后的架构——循环闭合

没有第 4 步,循环就是开放的。文档变得越来越虚构。Agent 越来越多地生成符合虚构架构的代码。差距随每次提交而扩大。

漂移检测是闭合这个循环的机制。

开始使用漂移检测

如果你没有架构文档

从 AI 发现开始。将你的代码仓库连接到 Archyl,运行发现功能,审查生成的 C4 模型。这会给你一个大约 70-80% 准确的基线模型。然后设置漂移检测来维持这个准确度。

如果你有现有文档

在支持漂移检测的工具中导入或重新创建你的架构模型。运行第一次漂移计算。评分会精确告诉你当前文档的准确程度——而分解报告会显示你应该首先修复什么。

如果你已经在跟踪漂移

将漂移检测集成到 CI 中。设置阈值。配置告警。开始跟踪趋势。将漂移作为团队指标,而非一次性审计。

无论你从哪里开始

最重要的是开始行动。架构漂移就像技术债务——它会随时间复利增长。你越晚解决它,追赶起来就需要越多的工作。但与技术债务不同的是,漂移检测可以在几分钟内设置好,并立即提供价值。

你的架构文档要么在反映现实,要么没有。现在你可以衡量它究竟是哪种情况了。


了解更多关于架构文档维护的内容:架构漂移评分的工作原理 | 什么是 C4 模型? | AI 驱动的架构文档。或者 免费试用 Archyl,在几分钟内计算你的第一个漂移评分。