离职有一段时间,还是过得挺开心的。终于有大把的时间可以做些喜欢的事。通过一段时间的学习沉淀,突然领悟到了一点东西。在此记录一下。
今天起来脑子里突然浮现出来2个关键字: 软件工程
、 控制复杂度
。这2个关键字在脑海中闪过,牵连起近期学到的知识,似乎一切都揭示了问题的本质——软件工程的核心就是控制复杂度。
复杂度是什么
在谈论复杂度之前,我们先谈谈熵。等一下,熵又是什么?
熵最初是热力学里面提出的概念,举个栗子。
我有一杯冷水,我有一杯热水。现在我把热水加到冷水里面。最终水的温度,将趋于恒定。
现在我想把冷水和热水分开,重新装入原来得杯子,很明显这是不可能的,这个过程是不可逆的。
整个系统趋近于无序。为什么是无序,因为整个系统内,已经没有热量传递了,全是温水往哪里传递热量?该系统将处于一个混沌的状态,死气成成。
热量的传递无时无刻不再进行,我们的宇宙最终会变成一个恒定无序的状态,里面的东西没有任何意义。
回归到信息论中来,熵被用来度量系统的随机性(不确定性)。又到了抛硬币时间,我有1个硬币抛一下,你有一半的概率猜中是正面还是反面。现在我有1万个硬币,你几乎不可能猜对了。
熵的概念应用到软件工程中其实非常容易理解。
我写了1行代码,你肯定很容易理解,知道代码是干嘛用的。
我写了10行的时候,你简单阅读一下就能理解。
我写了100行的时候,你可能要画个图,梳理一下流程才能理清楚。
...
我写了100000 行代码(bug一堆,我还没写注释,我溜了),接盘的程序员天天问候我家人,这是再正常不过的事情了。
现在有个百万行代码的项目,整个系统bug一堆,但是没有人敢动代码,这像不像一杯恒定的温水,死气成成,再无创新的可能呢?开发人员无法理解其意义,项目也就没有了意义。也就谈不上扩展和维护了。
软件的熵总是倾向于最大化的,程序员们称之为“软件腐烂”。 —— 《程序员修炼之道》
怎么控制复杂度
没有麦克斯韦妖,也没有银弹。我们这些凡夫俗子是怎么解决复杂度问题的呢?下面我们做这样的定义
软件工程(项目) = 软件系统 + 开发团队
**软件自成一个系统,开发软件的人是另外一个系统。**从一定程度上来说,软件工程中的复杂度跟代码量成正比,跟开发人员数量成正比。**封闭系统总是熵增的,**还好软件工程 是2个系统,开发团队可以介入软件系统,减少其熵。
开发团队是人,人是不断进化的,任何外部的信息都会影响到开发团队,所以开发团队是开放的系统。也就是说一个软件项目是一个开放的系统。所以复杂度还是可以控制的,至于控制效果的如何,就要看开发团队的水平。
两大法宝
- 聚——抽象
- 把相似的问题,抽象成一类问题,实现这类问题的公共解。代码量减少,代码复用,熵减。
- 总复杂度降低
- 散——分治
- 把复杂度高的问题分解成复杂度低的问题,使得问题容易处理。熵并不会减少,相反可能会增加。
- 总的复杂度并没有降低,而是单元复杂度降低。
在分治中,如果找了相似的子结构,又可以通过抽象,把相似子结构的部分合并。这样就是最好的解法了。
软件行业兴起这么多年来,前人总结了大量的经验来控制复杂度。我举几个简单的例子来说明他们为什么能控制复杂度。
- 在变量命名中,见名知意是一条很重要的准则。
- 名称即索引,好的名称便于检索。你掌握了名称也就掌握了它。
- 这一条既不属于抽象也不属于分治,但是它放在了第一条,因为代码是会被阅读的。名字共识是开发人员协作的基础,没有名字也就谈不上控制。
- 函数复用
- 功能相同的函数封装成通用函数,减少代码量。
- 黑盒封装
- 屏蔽内部细节,暴露接口。
- 不管你的代码写的多烂,把过程封装成函数,暴露等着别人调用即可,他人也无需关心盒子内的实现。这让开发人员得以抽身去关注更高层次的问题,构建更加复杂的系统。
- 模块化
- 代码量日益增长,模块化组织代码。
- 每种语言都会提供模块化的支持,这是必然的。
- 大模块划分成小模块,分而治之的思想在这里得到体现。
- 模块化也可以理解为高层次的黑盒封装。
- 在语言层面,有模块化的支持。在框架层面也有,例如 Django 会按照 app 来组织代码。这些都是标配。
- 如今前端开发自成体系,模块化是必然的。但最开始 JS 是不支持模块化的。后来 node 自己引入了模块实现。再后来,模块化在 ES6 中得到官方支持。模块化是控制复杂度必不可少的工具。
- 微服务
- 微服务的思想依然是分治
- 开发团队拆分,业务代码拆分。大系统拆分为小系统。
- 单元复杂度降低,但随之而来的是协作问题。
...
对于控制复杂度,大师们精于此道,他们给出了很多建议(最佳实践)。你若不准寻这些建议,你就是团队里面的捣蛋鬼,你写的代码再多,也只是暂时实现了功能,但是引入的却是难以消除的熵。
控制复杂度的方式还有很多,各种经典的书中都有提到,例如《程序员修炼之道》、《代码大全》等等。书中提出了很多原则,很多方法,以及各种最佳实践。他们告诉你如何写最好的代码,而这些代码为什么好,因为它们本质上都控制了复杂度/单元复杂度,当复杂度降下来,代码可读性、可扩展性就越好。整个项目“腐烂”的速度就越慢,生命周期就越长。
在代码之外减少变量
软件工程中每引入一个变量,熵就会随之增加。这里的变量不仅仅指代码中的变量,一个开发人员是一个变量,开发环境也是变量,语言框架亦是变量。很多时候我们引入变量,是为了快。但是《人月神话》告诉我们事情不是想象的那么简单。
尽可能地减少变量,或者实在不行就管理变量。
- 开发环境,测试环境,生产环境。每个环境都是一个变量。所以有了docker,有了容器,把他们抽象成一个环境。
- 后端开发,前端开发,每个开发都是一个变量。所以有了全栈,能少一个是一个。
- 需求,开发,测试,部署,运维。每一个环节都是一个变量。当每个环节都有不同的人时,复杂度成指数爆炸。所以有了 devops,有了全周期。
- 版本1.0,版本2.0。每一个版本都是一个变量。但是我们又无法消除版本。所以我们有了版本控制,有了 svn,有了 git。
用汇编也能写一个淘宝,但是没人会这样做。软件工程要求快,但也要保证其质量,所以诞生了越来越多的技术/思想/开发模式,来解决复杂度的问题,同时提高开发效率。
从交接文档看软件熵增
项目完结之后,软件系统趋于稳定。这个时候整个软件系统看起来是一个封闭系统。根据定义,封闭系统总是熵增的,那这个时候软件系统的复杂度应该是增加的,怎么理解这一点呢?
我们来看这样一个问题,一个项目已经完结,在线上跑了很久了。这个时候PM说要加个新功能。
- 完结后的一天PM说要加新功能,没问题,加。
- 完结后的一年PM说要加新功能,???。
我们都知道1简单,2困难。这里简单和困难的定义是,开发人员需要做多少功才能开始开发新功能。前者做的功少,后者做的功多。为什么呢?即使是完结的项目,它的熵也在增加。可是一时间我们竟想不出是哪里增加了,代码又没变动。是的,代码没有变动,但是人变了。开发人员没有对软件系统做功了。
软件工程(项目) = 软件系统+开发人员
回到定义,一个封闭的系统总是熵增的。需要外部对其做功才能使其稳定。项目文档,开发人员对项目的熟悉程度(记忆),等等都是外部对软件系统做的功。文档遗失,开发人员对项目的熟悉程度降低等等这些因素,都会使得完结的项目熵增。
如果要使得1天后加功能和1年后加功能同样简单。需要开发人员始终保持对项目的熟悉程度。即开发人员需要做功,维持熵稳定。如果有一天开发人员离职了,文档可以辅助新的开发人员熟悉项目。人的记忆力会降低,但是文字不会。文字可以跨越时空交流。这就是为什么离职需要写交接文档。
新成员加入后,为了实现新功能,需要先熟悉系统。而这个熟悉的过程就是在做功。文档是前辈做的功,这样新成员可以少做功。复杂度越低,新成员做的功就越少。对于企业而言,做功的代价就是花钱。
写在最后
在写这篇文章之前,我先以 软件工程
控制复杂度
为关键字检索了一遍,搜到下面这篇文章。
https://zhuanlan.zhihu.com/p/21885300
大家的结论都是——软件工程的核心是控制复杂度。
在文章写道一半时,我又搜索了一遍,找到了下面这篇文章。
https://www.infoq.cn/article/7vtun90yxpht-0hgdf72
第二篇中提到了**控制复杂性有两个策略。**跟我想的简直是一模一样,哈哈哈,这大概就是英雄所见略同吧。
读完《信息简史》后,看问题地角度就不一样了,在这里强烈安利一下 《信息简史》这本书。本篇文章很大程度上是受信息论的启发。用熵的观点去看待软件工程中的各种问题,与 SICP、《程序员修炼之道》、Rust文档中的观点,以及自己的从业经验相互印证,使我得出了这个结论。
当我以结论检索时,发现这个结论已经存在了。这让我很开心,知识果然都是共通的。