在纷繁复杂的概念满天飞舞的软件编程领域,希望能分享自己整理的一些观点,帮助大家穿透迷雾,看清问题以及解决方案的本质。
人类认知能力上限
有一个说法:人的短时记忆广度平均数为7,即大多数人一次最多只能记忆7个独立的块(Magic number 7)。
有另外一个说法:当需要根据短时记忆进行信息加工时,一个成年人能处理的块的个数是4左右(Working memory)。
这些说法不一定完全正确,但我们可以肯定的是,人类处理信息、加工信息的能力是有上限的。
软件系统的复杂性
然而要完成一个有实际意义的软件业务系统,绝对不是处理4个变量或者7个变量就能完成的,完整的业务其所需处理的数据要大几个量级。
由此软件系统的复杂性与人类认知能力存在上限形成了一个矛盾。当复杂度超过人类可控制的范围时,我们就可以认为其失控了。
我们要怎么避免软件系统失控?或者说如何正确可控地完成一个软件系统呢?大家可以先思考10秒。
既然矛盾是两个方面的事情,那解决矛盾也可以从两个方面入手:
-
一个是分而治之,将问题分解成人类可控的范围大小
-
一个是从软件复杂度方面出发,控制总体复杂度
分而治之
要进行分治的话,有也两个问题要解决
-
按什么逻辑分组
-
每个问题的规模大小
对于第一个问题的答案就是我们经常听到的
高内聚,低耦合
上述指导原则,能让紧密耦合的逻辑、数据归集到一起,能更少牵制(接口、协议)地进行内部演进。
而存在的 接口、协议 能让我们可以以更高维度的抽象来组织我们的逻辑。
就像我们组装电脑时,只要主板与CPU、硬盘、显卡等等的接口协议确定了,那么主板就可以不管各类组件的具体品牌,型号地组织不同部件的工作,而各个组件都可以在接口的控制下各自独立改进。
对于第二个问题,则是:
子问题规模应为单个人认知极限规模的70%左右
如上图,问题可以分解成不同的层级。上图把问题及解决方案分解成了3层,核心问题、子问题、孙问题。
之所以有层级的存在,是因为子问题的解若数量太多的话,一个人也是无法整合处理的,因此要处理的子问题解数量必须在一个人的可控范围内。但数量控制了,子问题的规模可能很大,并非一个人能求得,因此需要继续划分孙问题。
从上一段的文字我们也可以得出一个结论,子问题并非越小越好,而是尽可能的大,如此才能减少问题的层级,也减少了整合子问题而产生的消耗(也就是说微服务不是越小越好),而问题规模为个人处理极限的70%则是为了应对后续变化,需要预留一定的buffer。
总体复杂度控制
总体复杂度存在一个极限最小值,其由问题域的本质复杂度决定。
然而我们的解决方案会在本质复杂度的基础上,有意无意地引入其他很多额外的复杂度:
-
由于人类认知极限而进行子问题分解、整合而产生的复杂度
-
由于未透彻理解问题本质而引入的额外复杂度,如错误的设计而形成畸形的、绕了远路的解决方案
-
由于错误判断问题的演化方向而引入了额外的复杂度,这个可以理解为过度设计
-
由于一些问题域外的其他需求引入的额外复杂度,如为了监控、排查问题等等而引入的复杂度
-
由于代码腐化导致的复杂度,如复制代码块,补丁式代码等等
-
由于性能问题引入的复杂度
-
......
可以说,引入复杂度的原因有千万种。而我们能做的,只有一件事情——每次引入复杂度时都认真思考其是否必要,是否利大于弊。
然而我们不能指望于每个人都有能力以及觉悟去审视这个问题,因此自上而下的设计以及监督是必须的,否则代码的整体复杂度就会不断提升,与热力学第二定律有点类似
孤立系统的熵(混乱程度)永不自动减少,熵在可逆过程中不变,在不可逆过程中增加。
一个良好设计的系统,在后续没有经过上层设计以及监督(外部系统做功)的软件系统的混乱程度会不断增加,直到系统的混乱程度变得与编码的程序员思维混乱程度一致(当然或许编码程序员的思维并不混乱,对代码也有自己的要求,这样系统或许会不再腐化,甚至进化,但我们没办法做到每个程序员都有这样的水平)。
小结
本文的内容只是写好代码的序章,但由于规模引起的问题最终解决方向都会落到上面提到的 分而治之 以及 总体复杂度控制 上,这个是根源。
分而治之及总量控制不仅仅能在软件领域上应用,我们可以从 公司人事组织架构、天体系统研究等等方面都可以看到应用。
后续文章我会尝试在上面两个方向上继续细化,将 封装、抽象、多态、分层、DDD 等等思想 以及 面向对象语言、微服务、中台等等外功 关联起来。
若本文能给你带来帮助,请点击右下角在看,若本文存在谬误,请帮忙批评斧正,谢谢!
作者简介
多年金融行业经验,现为某Top2互联网银行高级搬砖工,曾在两家TOP3股份制商业银行及一家互金创业公司工作(架构、核心业务主程),EasyTransaction作者,欢迎关注个人公众号,在这里我会分享日常工作、生活中对于架构、编码和业务的思考