跳转到内容

架构太耗时间

听起来很合理。

“我们现在没时间做架构。”

大多数时候,这句话并不意味着: 我们节省了时间。

它更常意味着: 我们把决策往后推了。

被推迟的架构决策不会消失。它们会在之后重新出现——变成耦合、难以测试的代码、模糊的职责、无休止的协调,以及对修改的恐惧。

架构需要时间。 但缺少架构同样需要时间。

只是后者很少在项目一开始就出现在计划里。

缺少架构很少真正节省工作量,它通常只是把成本推迟到更昂贵的阶段。

这个误区来自一种看法:把架构当成开发之外的额外工作。

先做 Feature。 然后再做架构。 如果还有时间的话。

软件并不是这样运作的。

每个代码库都会形成结构,即使从来没人有意识地设计它。文件总会放在某个地方,组件总会认识某些东西,数据总会以某种方式流动,副作用总会发生在某处,决策也照样会被做出——只不过它们变得隐式、局部,而且常常彼此矛盾。

所以,“没有架构”并不是一种中立状态。

没有架构,本身也是一种架构。 通常是一种偶然形成的架构。

Big Ball of Mud 很少源于某一个糟糕决定

Section titled “Big Ball of Mud 很少源于某一个糟糕决定”

Big Ball of Mud 很少是在某个星期二 14:37,因为有人决定:

“今天我们来做一个不可维护的系统。”

它通常来自许多单独看起来都很合理的小决定。

一个快速修复。 一个“只针对这次 Feature”的例外。 一个“之后会删掉”的依赖。 一次因为眼下更简单而做的直接访问。 一个再多承担一点职责的组件。 一个比它本来应该知道得更多一点的 Service。

每个决定都在短期内节省了时间。

但这些决定叠加起来,就会形成一个每次修改都需要越来越多上下文的系统。到某个阶段,你需要理解的已经不只是当前 Feature,而是之前所有捷径留下来的历史。

速度就是在这里开始反转。

不是因为团队突然变差了。 而是因为系统结构让每次修改都越来越贵。

最危险的架构决策,往往是那些从来没有被当成“决策”看待的决定。

好的架构并不是试图完美预测未来。

那才是 Overengineering。

好的架构,是有意识地设计那些未来很可能发生变化、或者一旦变化就会非常昂贵的地方。

业务边界在哪里? 谁拥有哪一部分 State? 哪些数据可以进入哪一层? 副作用在哪里发生? 什么是 Command,什么是 Read State? 什么属于 UI State,什么属于业务 State? 哪些部分可以彼此了解,哪些不应该?

这些不是学术问题。

它们决定了一次修改是局部完成,还是扩散到半个应用。

边界在一开始确实需要一些额外思考。

但它们会减少之后的协调成本。

如果一个模块的职责清晰,就不必在每个新 Feature 中重新讨论逻辑到底应该放在哪里。如果 ViewModel 与 DTO 有明确边界,一次 API 变化就不必一路泄漏到 Template。如果副作用没有藏在组件的某个角落,它们就更容易测试、观察和调整。

这里说的架构,不是一张庞大的象牙塔式架构图。

架构是在回答一个问题:

什么必须保持稳定,才能让修改继续保持便宜?

好的边界不会阻止变化,而是让变化能够被局部化。

当然,架构也可能做得太重。

可以在问题出现之前就建立抽象。 可以把 Pattern 一层层叠起来,让简单问题看起来很重要。 可以用没人解释得清的规则拖慢整个团队。 也可以把“架构”当成不交付任何东西的理由。

这些问题都真实存在。

但 Overengineering 不是反对架构的理由。

它只是反对糟糕架构的理由。

过度架构的替代方案,不是完全没有架构。更好的替代方案是轻量级架构:它应该可见、可验证、团队能够共同使用,并且可以随着认识变化而调整。

Overengineering 的替代方案不是混乱,而是与问题相匹配的架构。

轻量级架构并不等于:

“大家想怎么做就怎么做。”

它意味着:

  • 少量而清晰的规则
  • 明确的职责
  • 可以验证的边界
  • 短反馈周期
  • 能够在代码中重新找到的架构决策

一条架构规则,并不会因为写进文档就自动成为好规则。

它真正有价值,是因为它能在日常工作中帮上忙: Code Review、测试、Refactoring、Onboarding,以及下一次修改。

捷径有时完全合理。

Prototype 可以和长期运行的产品采用不同结构。 Experiment 不必一开始就拥有核心业务流程那样的架构。 团队也应该允许先学习,再决定最终边界。

真正的问题是:临时决定变成永久结构之后,没有人再重新评估它。

于是,“这次先这样”变成 Pattern。 Pattern 变成习惯。 习惯最终变成架构。

只是没人这样称呼它。

缺少架构,不会让第一次交付变贵。

它让第十次修改变贵。

它不会让第一个 Button 很难实现。

真正困难的是后来同一个 State 同时被三个页面、两个 Store、一个 Resolver、一个 Effect 和一个组件修改的时候。

它不会让第一个 DTO 立刻变得危险。

真正危险的是 API 结构已经不受控制地嵌进 Template、Form 和组件逻辑的时候。

它也不会让第一次 Shortcut 就成为灾难。

灾难发生在没人再知道还有哪些 Shortcut 仍然生效的时候。

架构成本通常是可见的,而结构丢失的成本常常伪装成 Feature 开发成本。

与其说:

“架构太耗时间。”

不如更准确地说:

“我们需要决定,这个问题现在需要多少结构,才能保证以后依然改得动。”

这会把讨论带到完全不同的方向。

少一点信仰之争。 少一点英雄主义。 也诚实得多。

架构不是目的本身。 架构也不能替代产品理解。

但缺少架构并不等于速度。

它是一笔贷款。

Big Ball of Mud 就是那张最终账单。