复杂度不会消灭,但架构会决定偶然复杂度构成。

软件行业有一个广泛流传的认知误区:优秀的架构可以彻底消灭系统复杂度。很多人期待找到一套银弹架构,把所有复杂问题全部消解,写出永远简单的系统。但现实恰恰相反:业务固有的本质复杂度无法被消除,架构做不到消灭复杂度,它只能重新分配、重塑偶然复杂度的形态与位置。

布鲁克斯在《没有银弹》中把软件复杂度划分为两类:本质复杂度(本质性复杂度)偶然复杂度(附带复杂度)

本质复杂度来源于业务领域本身,是业务规则、流程、约束与生俱来的困难。例如金融系统的账务校验、制造行业的生产流程、电商的订单履约逻辑,业务规则本身就盘根错节,无论选择什么技术方案,这份客观存在的复杂都躲不开,它是问题自带的属性,架构再精巧也无法把它彻底抹除。

偶然复杂度,并不是业务本身自带的,而是我们在实现过程中,由架构选型、技术方案、组织分工、工具链路人为制造出来的额外负担。这部分复杂度才是架构真正可以发力的战场。架构不能消弭本质复杂度,但可以选择:把偶然复杂度放在代码内部?放在服务之间?交给运维团队?还是下压到底层中间件?不同的架构,只是偶然复杂度的构成要素、藏身之地发生了改变,总量未必消失,只是换了一副模样存在于系统之中。

单体与微服务:复杂度只是换了个家

最典型的例子就是单体架构和微服务架构的取舍。

单体架构,把所有业务收拢在一个工程内。它的偶然复杂度大量收敛在代码层内部:模块耦合、代码臃肿、大版本发布风险、单库性能瓶颈。但它换取的是分布式带来的偶然复杂度大幅降低:没有服务间远程调用、不用处理服务发现、不用关注链路追踪、不用维护多服务部署、不用解决分布式事务、多服务之间的团队协调成本很低。出问题可以直接本地调试,整条调用链路一目了然。

很多团队看到单体的代码臃肿,便全盘拥抱微服务,期待微服务能把复杂度全部干掉。但实际落地后会发现:代码内的耦合被拆解消解了,但是大量新的偶然复杂度转移到了服务之间。

微服务把大系统拆成一个个独立服务,解决了单体代码臃肿、模块互相干扰、按模块独立扩容的问题。但是换来一整套全新的偶然复杂度:服务调用超时、重试、熔断降级、分布式事务、链路追踪、多服务运维、多团队接口对齐、配置中心、注册发现、日志归集、多版本兼容问题。原来写在同一个代码文件里的复杂度,转变为网络、接口、运维、跨团队协作层面的复杂度Habr。

很多团队踩坑的根源就在于一种错觉:拆分等于消除复杂度。拆分不会让复杂度凭空消失,它只是把藏在代码里的复杂,搬迁到网络与协作层面。架构的抉择,本质是选择你愿意承受哪一类偶然复杂度,规避你无法承受的另一类偶然复杂度。

不存在绝对更优的架构,只存在适配当前业务规模、团队能力的复杂度分配方案。小团队强行上微服务,业务体量不大,却要背负全套分布式的偶然复杂度,属于典型的架构过载;规模庞大的业务死守单体,任由代码耦合持续膨胀,让全部风险堆积在代码内,同样是架构的失职。

架构的核心使命:管理偶然复杂度,隔离本质复杂度

既然本质复杂度无法消灭,架构的目标就不是追求 “零复杂”,而是两件事:

  1. 尽可能裁剪不必要的偶然复杂度,剔除人为制造的无效负担;
  2. 对无法消除的复杂做位置调度,把复杂收敛、隔离在最合适的地方,不让复杂度无约束地扩散到整个系统。
    好的架构会把本质复杂度收拢、封装在模块内部,对外提供简洁稳定的接口,把脏活累活封闭在内,对外只暴露简单。内部再怎么千头万绪,不要把复杂泄漏给上游调用方。

而糟糕的架构会做相反的事情:把偶然复杂度四处扩散。底层的存储细节、中间件特性、异常逻辑一路向上泄漏,每一层都被迫感知下层的复杂,最终整个系统到处散落各式各样的附带问题,到处都是偶然复杂度,小小的改动就牵一发而动全身。

很多系统随着迭代慢慢变成 “大泥球”,并不是需求天生无比复杂,而是在一次次迭代中,没有通过架构约束偶然复杂度,人为不断叠加各类临时方案、临时补丁,让偶然复杂度无限生长,和业务本质复杂度混杂在一起,最后系统难读、难改、难排错。

要警惕一种幻觉:换框架、换架构、换技术栈可以根治系统复杂。如果业务的本质复杂度还在,仅仅更换架构,只是把旧的偶然复杂度清除,又会生长出新形态的偶然复杂度。架构更迭是重构复杂度的构成,不是消灭复杂度。

如何理性看待架构选型

理解 “架构不会消灭复杂度,只会决定偶然复杂度构成”,能帮我们跳出很多技术圈的跟风陷阱,建立更务实的架构思维。

  1. 不要迷信架构万能论
    没有任何架构可以让复杂业务变得 “完全简单”。选型前先梳理清楚:这套架构会新增哪些偶然复杂度?现有系统哪些痛点可以被解决?新增的代价,团队能不能扛得住?不要只看收益,忽略转移过来的新负担。
  2. 分清本质复杂度与偶然复杂度
    遇到系统难维护的时候,先做区分:这份复杂来自业务本身的客观规则,还是架构、实现、历史债务带来的额外麻烦。对于偶然复杂度优先治理;而本质复杂度只能做好隔离封装,不要妄想彻底消灭。
  3. 复杂度的位置,要匹配团队能力
    如果团队运维人力薄弱,就不要主动把大量复杂度推向运维侧;如果团队开发能力强,可适当将部分复杂收敛在代码层;如果跨团队协作成本极高,就要尽量减少服务拆分带来的接口协调负担。架构不是纸上的漂亮图纸,复杂度放在哪里,就要对应的人来承接这份成本。
  4. 持续治理偶然复杂度,拒绝无限堆积
    本质复杂度跟随业务发展会逐步增长,但偶然复杂度是可以持续修剪的。每一轮迭代,除了完成新需求,也要清理历史累积的临时方案,避免偶然复杂度越堆越高,和业务固有复杂纠缠在一起,最后系统彻底失控。

写在最后

软件系统不存在通往彻底简单的捷径。业务固有的本质复杂度客观存在,架构无法将它彻底抹除。

架构真正的价值,不是创造一个没有复杂的乌托邦,而是重新洗牌偶然复杂度:选择哪些偶然复杂度保留,哪些要剔除,哪些放在代码,哪些交给中间件,哪些交给运维与组织。复杂度不会消灭,但架构会决定偶然复杂度构成。

评判一套架构好坏,从来不看它是否时髦、概念是否炫酷,而是看它是否把偶然复杂度控制在团队可承受的范围之内,把不可避免的本质复杂度妥善隔离,让系统在业务不断增长的同时,依然保持可理解、可修改、可演进。