全部文章
Architecture 软件设计 架构

软件设计(二):复杂性

更新于 2026年7月29日 1,851 字 约 5 分钟
ARCHITECTURE 软件设计(二):复 杂性 软件设计 · 架构 § 04
阅读进度 0% 继续阅读

一.人脑与复杂性的战争

写软件的唯一约束,是我们理解自己所造出来的系统的能力,系统越大复杂性就越高,直到没有人能够改的动。比如在AI编程的时候往往生成一堆代码加了很多功能,但要改的时候就会发现复杂性爆炸了,甚至还可能会经历多次重构。

什么是复杂性:复杂性是软件结构中任何让它难以被理解和修改的东西,由依赖和晦涩两种毒素一点点积累而成。

系统每加入一个功能,组件之间的微妙依赖就会变多些,直到没人能够把相关因素同时装进脑子,从而出现开发变慢,bug变多,开发更慢的情况,在任何程序的生命周期里必然发生,只是多快的问题。

对抗复杂性往往有两条路:

1.消除它:让代码更简单,更明显,消灭特殊情况,统一命名和惯例,把错误定义到不存在,适合复杂性还没有扎根的地方

2.封装它:模块化,让人一次只面对一小块

 

瀑布模型:软件工程史上第一个被正式提出的开发流程模型,名称和它的形态很像,需求分析->设计->实现->测试->维护,每个阶段像瀑布一样逐级落下,前一个阶段彻底完成才允许进入下一个,原则上不回头。

当然瀑布模型的前提是三层假设:

第一层:需求可以在开工前被完整,正确的知道

第二层:设计可以被脱离实现一次性做对

第三层:回头的成本可以不计,甚至干脆不需要回头

瀑布模型对软件几乎从不奏效,用户往往是看到不想要的东西,才知道自己不要什么,需求在交付前的任何时刻都只是个半成品,没人能在动工之前就预言设计的全部后果,导致后续只能通过打补丁解决,复杂性爆炸。

增量式开发:不一蹴而就的造整个系统,而是把系统切成一个个小的,完整可交付的功能增量,一次做一个,每做完一个系统就大一圈,而每一圈都是能够真实运行的。

注:增量≠迭代,迭代是先给整幅画打个潦草的草稿,然后一遍遍加深、修正、细化,每一笔都覆盖全画面;增量是把画面分成区域,把左上角那块彻底画完,再画下一块,每块一次到位。

增量式开发是比较有效的方法,因为软件足够软,每一轮迭代暴露的设计问题,都可以在系统还小的时候就修掉。

二.复杂性到底是什么?

这里分为三层来进行回答:

2.1.实用定义

复杂性 = 与软件系统结构有关的、让系统难以理解和修改的任何东西。注意是「结构」——算法本身难(比如一个精巧的数值算法)不算本书说的复杂性;代码组织方式让人难懂才算。判断标准极简:改起来费劲,就是复杂;改起来顺手,就是简单。

2.2.一个可以算账的公式

C=∑pcp⋅tpC = \sum_{p} c_{p} \cdot t_{p}

C  = 你作为一个开发者,实际承受的系统总体复杂性 p  = 系统中的某个部分(模块 / 文件 / 类) cp = 部分 p 本身的复杂度(它有多难懂) tp = 开发者花在该部分上的时间比例(0 ≤ tp ≤ 1,Σtp = 1)

举个例子,假如系统有两块代码

模块 A:又臭又硬,cA = 9,但一年才碰一次,tA = 0.02  →  贡献 9 × 0.02 = 0.18
模块 B:还算干净,cB = 2,但每天都在改,tB = 0.50  →  贡献 2 × 0.50 = 1.00

// 把 A 从 9 重构成 3:总复杂性只降 0.12
// 把 B 从 2 打磨到 1.5:总复杂性降 0.25 —— 投入产出翻倍的修法

从这个公式中存在两个结论:

①.重构不看哪里最烂,而是看烂×碰得多

②.把复杂性隔离到没人碰到地方等于消灭了复杂性

2.3.复杂性是读者的体验而不是作者的

你写的代码你觉得简单,别人觉得复杂——那就是复杂,以读者为准。这很残酷但合理:代码被阅读的次数远多于被编写的次数,你的工作是写出别人也能轻松上手的代码。

三.复杂性发作的三种症状

3.1 变化放大

一个看起来简单的改动,需要在很多个地方改代码。改个背景色要动 1000 个页面——需求本身没变复杂,是你的结构把一次决定复制了一千份。

3.2 认知负荷

完成一个任务前,开发者必须先在脑子里装进多少知识。C 函数返回的内存要调用者 free——每个用的人都得记住这条,忘一次就是一个内存泄漏。

3.3 未知的未知

你不知道自己需要知道什么:要改哪些地方不明显、需要哪些信息不清楚,甚至「有没有坑」都无从判断——直到改完上线、bug 炸出来才发现。

 

而其中未知的未知是最坏的,前面两种都只需要知道改哪里,改完就可以了,未知的未知往往意味着无法确认自己是否改全了,除非读遍系统每一行。

好设计最重要的目标:让系统显而易见

代码行数 ≠ 复杂性。有些框架让你三行代码写出一个应用,但那三行到底该写什么,要翻两天文档才搞明白——行数少了,认知负荷爆炸了。反过来,多写几行直白的代码把认知负荷降下来,往往才是真的「简单」。以后看到有人拿「我这个方案只要 N 行」当卖点,记得追问一句:想出这 N 行,需要先知道多少东西?

四.复杂性的病因

复杂性的病因只有两个:

1.依赖

2.晦涩

4.1 毒素A:依赖(没办法单独理解或修改)

一段代码和另一段代码有关联,改它时必须连带着考虑/修改对方。方法签名改了,所有的调用点都需要跟着被改。

注:依赖不可能被消灭,每次写一个类都是在主动引入围绕它API的依赖,设计的目标是:让依赖更少,让剩下的依赖更简单,显性

4.2 毒素B:晦涩(重要信息不明显)

变量名叫time(单位是秒还是毫秒?),新增一个错误码后还要去某张消息表里加一行——但那张表的存在,看代码的人根本不知道。不一致性是晦涩的头号帮凶:同一个名字干两件不同的事,读者永远猜不准这次是哪件。文档不足会造成晦涩,但需要海量文档本身就是设计有问题的红旗——治本靠简化设计。

 

 

复杂性 = 难理解 + 难修改;病因只有 依赖 和 晦涩 两种;它靠你每一次「无所谓」积少成多。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

目录