你最重要的架构决策,不会经过架构评审

43 阅读15分钟

一个暴论

那些对系统和文化影响最深远、最关键的架构决策,往往不是通过正式的、显性的架构评审会议来做出的,而是通过日常工作中那些无意识的、累积的、看似微不足道的“隐性决策”逐渐形成的。

一些案例

案例 1:微服务架构中,“默认同步调用”的隐性决策导致的雪崩效应

背景: 一个大型电商平台正在从单体架构向微服务架构转型。团队被鼓励快速拆分服务,以提高开发效率和部署速度。

  • 隐性决策: 在服务拆分初期,为了开发方便和快速验证功能,团队成员在设计服务间通信时,普遍采用了同步 HTTP 调用作为默认方式,并且没有强制要求实现熔断、降级或重试机制。理由是“这样最简单,先跑起来再说”。

  • 行为影响:

    • 服务 A 需要调用服务 B,服务 B 需要调用服务 C,形成了一条深层的同步调用链。
    • 开发人员习惯了这种直接的调用方式,没有意识到其潜在的风险。
    • 在测试环境中,由于负载低,这种模式没有暴露问题。
  • 架构问题:

    • 单点故障扩散: 一旦调用链中任何一个下游服务(如服务 C)出现性能瓶颈或短暂故障,服务 B 会被阻塞,进而服务 A 也会被阻塞。请求堆积,最终导致整个链路上的服务全部响应缓慢甚至崩溃,形成雪崩效应
    • 资源耗尽: 大量被阻塞的请求会长时间占用上游服务的线程和连接资源,最终耗尽服务资源,使其无法处理新的请求。
    • 难以定位故障: 当系统出现故障时,由于调用链复杂,且缺乏分布式追踪和熔断降级机制,故障定位变得极其困难,往往需要花费数小时甚至数天。
    • 扩展性受限: 即使某个服务能够独立扩展,但由于同步依赖,其整体吞吐量仍然受限于调用链中最慢的服务。
    • 业务中断: 在大促等高并发场景下,这种隐性决策直接导致了整个核心业务(如订单创建)的间歇性甚至长时间中断。

这个决策并非在任何架构评审中被明确批准“我们就是要用同步调用,不加熔断”。它是在日常开发中,由“方便”、“快速”的隐性偏好,以及缺乏对分布式系统复杂性深刻理解的默认行为累积而成的。当问题爆发时,解决它需要对大量服务进行改造,引入消息队列、熔断器、重试机制、分布式追踪等复杂组件,成本巨大且风险极高。


案例 2:数据湖建设中,“快速摄入”的隐性决策导致的“数据沼泽”

背景: 一家公司决定建设数据湖,旨在汇集所有业务数据,为数据分析和机器学习提供基础。目标是快速将所有数据源接入。

  • 隐性决策: 为了快速实现数据摄入(Ingestion)目标,团队默认将所有源数据不做任何清洗、标准化或元数据管理,直接以原始格式存储到数据湖中。理由是“先存起来,以后再处理”。同时,缺乏对数据生命周期、数据质量和数据治理的明确规划。

  • 行为影响:

    • 数据工程师专注于将数据从源系统搬运到数据湖,而忽略了数据的可用性和可理解性。
    • 业务分析师和数据科学家发现数据湖中的数据杂乱无章,难以使用,需要花费大量时间进行数据探索和预处理。
    • 不同团队对同一概念(如“用户ID”、“订单状态”)有不同的定义和存储方式。
  • 架构问题:

    • “数据沼泽”形成: 数据湖变成了存储原始数据的垃圾场,数据量巨大但价值难以挖掘,被称为“数据沼泽”(Data Swamp)。
    • 数据质量问题: 缺乏数据清洗和校验,导致数据中包含大量错误、缺失值和不一致性,严重影响分析结果的准确性。
    • 数据孤岛: 尽管数据物理上集中在一起,但由于缺乏统一的元数据管理和语义层,不同数据集之间难以关联和整合,形成逻辑上的数据孤岛。
    • 高昂的分析成本: 数据分析师和数据科学家需要投入 80% 的时间在数据准备上,而不是在分析和建模上,大大降低了数据价值产出的效率。
    • 信任危机: 由于数据质量问题,业务部门对数据分析结果失去信任,数据湖的价值无法体现。
    • 治理难题: 随着数据量和复杂性的增加,后期再想进行数据治理和标准化,需要投入巨大的人力和时间成本,甚至可能需要推倒重来。

这个隐性决策并非是“我们故意要建一个数据沼泽”。它源于对“数据湖”概念的误解(认为只是一个大存储),以及在“快速完成任务”的压力下,默认放弃了数据质量、标准化和治理的思考。最终导致了数据资产的贬值,甚至成为公司数据战略的巨大障碍。


案例 3:敏捷开发中,“非功能需求”的隐性降级导致的系统瓶颈

背景: 一个快速增长的 SaaS 产品团队采用敏捷开发,以两周一个迭代的速度交付新功能。

  • 隐性决策: 在每个迭代的规划中,由于产品经理和开发团队的重心都放在“用户可见的功能”上,非功能需求(NFRs,如性能、安全性、可观测性、可伸缩性、可维护性)往往被口头提及,但没有被明确地作为独立的任务排期,也没有被分配足够的时间和资源。这导致了“NFRs 优先级低于功能需求”的隐性决策。

  • 行为影响:

    • 开发人员为了在迭代内完成功能,会选择最快的实现方式,即使它可能牺牲性能或可维护性。
    • 安全审计、性能测试等工作被推迟到“以后再做”。
    • 监控和日志系统只是基本搭建,没有深入考虑故障诊断和性能分析的需求。
  • 架构问题:

    • 性能瓶颈: 随着用户量增长,系统响应速度变慢,甚至出现卡顿和超时,用户体验急剧下降。
    • 安全漏洞: 由于缺乏定期的安全审查和加固,系统存在高风险的安全漏洞,可能导致数据泄露或服务被攻击。
    • 可观测性差: 当系统出现故障时,缺乏足够的日志、指标和追踪信息,导致故障排查困难,恢复时间长。
    • 技术债务堆积: 系统的可维护性差,每次修改都可能引入新的 bug,开发效率越来越低。
    • 扩展性危机: 当业务需要快速扩展时,系统架构无法支撑,需要进行大规模重构,耗时耗力,错过市场机会。
    • 用户流失: 糟糕的用户体验和频繁的故障导致用户大量流失,直接影响业务增长。

这个隐性决策并非是团队故意要构建一个性能差、不安全的系统。它是在“快速交付功能”的显性压力下,通过日常的排期、任务分配和优先级选择,无意识地将非功能需求降级。最终,这些被忽视的非功能需求成为了系统的致命弱点,在关键时刻爆发,对业务造成了不可逆的损害。解决这些问题往往需要暂停新功能开发,投入大量资源进行“还债”,甚至可能错失市场窗口。

这些例子都说明,隐性决策并非总是“坏”的,但它们往往是未经深思熟虑、未经明确共识、且缺乏持续关注的。它们在日常的“小选择”中累积,最终形成巨大的架构问题,其影响可能比任何一次正式的架构评审决策都要深远和致命。

为什么这些问题无法被架构评审捕捉?

1. 架构评审关注的是“显性”决策

架构评审会议通常讨论的是可见的、明确的、文档化的问题:

  • 技术选型: 我们应该用 PostgreSQL 还是 MySQL?用 React 还是 Vue?
  • 系统蓝图: 系统的模块如何划分?服务之间的接口(API)如何定义?
  • 关键流程: 核心业务逻辑的数据流是怎样的?

这些无疑是重要的决策。但它们是在一个特定的时间点,基于当时可获得的信息做出的“快照”。

2. “隐性”决策持续塑造着架构

然而,在这些正式评审之外,大量“隐性”的、未经讨论的决策每时每刻都在发生,它们像一种持续的引力,不断地塑造和改变着架构的实际形态:

  • 指标的选择 (What you measure is what you get):

    • 如果团队的 KPI 是“功能交付数量”,工程师就会倾向于快速开发,可能会牺牲代码质量、可维护性和测试覆盖率。这就在无形中决定了架构会变得越来越脆弱和混乱,无论评审会议上多么强调“高质量”。
    • 如果衡量的是“Kafka 消息发送量”,团队就会想办法发送更多消息,哪怕其中很多是无用数据,这直接导致了系统资源的浪费和不必要的复杂性。
  • 默认设置和工具的影响 (The "Default Trap"):

    • 缓慢的 CI/CD 流水线是一个隐性的架构决策。它“决定”了工程师会减少提交频率,并将多个功能捆绑在一个大的PR中,这使得代码审查变得困难,系统集成风险增高,与微服务、小步快跑的架构原则背道而驰。
    • “每个团队都应该拥有自己的技术栈” 如果成为一个组织的默认文化,它就“决定”了整个公司级别的架构会走向碎片化和高成本,即使没有任何一次架构评审明确“批准”了这种模式。
  • 日常行为和团队文化:

    • 工程师是否愿意进行重构?如果团队文化不鼓励或不允许重构(因为“没有带来新功能”),那么架构的腐化就是必然的,这是一个未经评审的、由文化做出的架构决策。
    • 解决问题的倾向是**“再造一个轮子”还是复用现有方案**?这种倾向直接决定了系统的复杂度和维护成本。

结论

最重要的架构决定,不会经过架构评审,因为:

  • 范围不同: 架构评审关注的是“设计时”的静态蓝图,而隐性决策影响的是“运行时”的动态演进。
  • 频率不同: 架构评审是低频事件,而隐性决策是高频的、持续不断的。
  • 影响力不同: 隐性决策通过改变人的行为,从根本上、持续地影响着代码的最终形态和系统的健康状况。它们的力量往往比一纸设计文档更为强大和持久。

一个在评审中设计得再完美的架构,如果在一个充满不良“隐性决策”的环境中实施,其最终的形态也几乎必然会偏离初衷,甚至走向崩溃。因此,识别和管理这些“隐性决策”与进行正式的架构评审同等重要,甚至更为关键。

这个问题无解吗?

当然不是。要识别、分析和解决隐性决策带来的问题,我们需要一套系统性的方法,将这些无形的影响力转化为可管理、可优化的显性因素。这可以分为以下几个步骤:

一、识别隐性决策 (Identification)

识别隐性决策是解决问题的第一步,它要求我们跳出日常惯性思维,主动去发现那些“看不见的”影响:

  1. 提出“不适的问题”:

    • 审视激励机制: 我们的工具、流程或指标,实际上在激励团队做出哪些行为?这些行为是否与我们期望的架构目标一致?例如,"我们的 CI/CD 流水线速度慢,是不是导致了工程师合并大PR?"
    • 质疑默认设置: 哪些默认设置是我们从未审视就接受的?这些默认设置是否依然合理?例如,"为什么我们团队总是倾向于自己造轮子,而不是复用公司内部已有的服务?"
    • 反思衡量标准: 我们正在衡量的指标,是否真正反映了我们想要达成的目标?它是否可能导致团队“为了指标而优化”?例如,"我们衡量 Kafka 消息量,是不是导致了发送不必要的事件?"
  2. 观察行为模式与结果:

    • 当团队出现持续的、难以解释的低效、质量问题或文化冲突时,往往是隐性决策在作祟。例如,代码库持续腐化、系统复杂度不断上升、团队士气低落等。
    • 注意那些“大家都在做,但没人说得清为什么”的行为。
  3. 追溯问题根源:

    • 当一个问题出现时,不要只停留在表面解决方案,要深入挖掘其背后的原因。例如,如果部署频繁失败,除了修复 bug,还要问:“是不是 CI/CD 流程的某个隐性假设导致了这些 bug?”

二、分析隐性决策 (Analysis)

一旦识别出潜在的隐性决策,就需要深入分析其影响和机制:

  1. 明确决策内容: 将隐性决策用清晰的语言描述出来。例如,不是“CI/CD 慢”,而是“团队为了避免长时间等待,隐性地决定合并大 PR”。
  2. 分析其影响: 评估这个隐性决策对系统、团队、业务和未来发展带来的具体积极和消极影响。例如,合并大 PR 导致代码审查困难、集成风险增加、部署周期变长。
  3. 探究其形成原因: 为什么会形成这样的隐性决策?是历史遗留、工具限制、团队文化、个人习惯、还是错误的激励机制?例如,CI/CD 慢可能是因为基础设施不足,也可能是因为测试流程过于臃肿。
  4. 关联到“决策质量”: 思考这个隐性决策是否是“高质量的决策”?它是否在当时或现在是最佳选择?

三、解决隐性决策带来的问题 (Solution)

解决隐性决策问题的核心在于将其显性化,并进行有意识的干预

  1. 将隐性决策显性化:

    • 命名与讨论: 将识别出的隐性决策明确地命名,并在团队中公开讨论。这是最关键的一步。
    • 架构决策记录 (ADR): 对于那些被显性化后认为重要的决策(无论是保留、修改还是废弃),可以考虑使用 ADR 进行记录。这有助于团队理解决策的背景、理由和影响,避免未来再次陷入同样的隐性决策。
  2. 挑战和改变默认设置:

    • 重新评估默认: 对那些未经审查的默认设置进行评估,看看它们是否仍然符合当前的需求和目标。
    • 主动设置新默认: 如果发现某个默认设置是负面的,要主动设计并推行新的、更健康的默认行为。例如,通过技术手段限制 PR 大小,或优化 CI/CD 速度,从而改变工程师的隐性行为。
  3. 调整激励机制和衡量标准:

    • 重新设计 KPI: 确保团队的绩效指标与期望的架构健康度、代码质量、协作效率等目标对齐。避免只关注短期功能交付。
    • 衡量决策质量: 除了衡量结果,也要尝试衡量决策过程和质量。例如,定期回顾一些关键决策,评估其长期影响。
    • 奖励期望行为: 明确奖励那些符合期望行为的团队和个人,例如积极进行小步提交、高质量代码审查、主动重构等。
  4. 优化工具和流程:

    • 改进基础设施: 解决导致隐性决策产生的工具或基础设施限制。例如,投资更快的 CI/CD 系统。
    • 简化流程: 移除不必要的复杂流程,减少工程师绕过正常流程的动机。
  5. 培养批判性思维和持续学习文化:

    • 鼓励团队成员始终保持批判性思维,质疑现状,不盲目接受默认。
    • 建立持续学习和改进的文化,定期进行回顾和反思,将识别和解决隐性决策作为团队持续改进的一部分。

通过这些步骤,团队可以从被动地受隐性决策影响,转变为主动地识别、分析和塑造这些决策,从而构建更健壮、更可持续的软件架构和更健康的工程文化。