复杂系统真相——软件篇

最近国内很不巧,赶上了新型冠状病毒SARS-Cov-2大爆发,也算是得益于此我获得了自打从英国回国入职后久违的真正意义上的小长假。当然,按照国内科技私企的特点免不了之后会做出让我问候他家祖先的弥补性措施来。不多扯题外话,主要就复杂的系统谈一谈我的个人浅见。

亘古至今,从旧石器时代远古人类的群居生活到新石器时代晚期部落的建立再到现代信息化的社会,人造复杂系统经历了至少成千上万年的演化在生活中已无法离开、无处不在:

  • 大城市本身
  • 交通网络
  • 通讯系统
  • 大飞机、航空母舰等大型单体机械设备
  • 社会公务系统等

除上述的不完整的例子外,还有更神奇的数十亿年演化的自然体系:

  • 地球生态系统
  • 行星系统等

我不是研究自然科学的专家,在此也不谈系统设计(其实系统设计更值得玩味),主要就我相对比较熟悉的复杂软件系统给出一点判断和理解。直到撰文的此刻,再有两个月我工龄将满3年,在2家公司打工,先后经手了5个项目。姑且不论5个商业性质的项目和50个是否会有本质性的区别,一些很容易发现的规律还是大差不差。

背景

软件系统的特征

这里的软件是指代功能完备,可独立运行的非单一代码片段的程序集。最小单位显然就是单文件的软件,但是单一文件的软件可以非常复杂,比如纯C实现的「猫版马里奥」,下面的例子是C++实现的最小的软件之一:

1
2
3
4
5
6
7
8
#include <iostream>

using namespace std;

int main() {
cout << "Hello World" << endl;
return 0;
}

功能确实非常简单,也算是个软件了。而软件系统一般就比上述复杂了,起码功能模块不少于2个或以上,讨论软件系统的定义不是特别有意义,这里就当读者都已经理解系统的定义。一些例子:操作系统、网络系统。

战略开发与战术开发的特点

战术开发通常着眼与眼前,见效快,但是对于未来的投入影响小。战略开发则有延迟满足的特点,一般是对于未来的投资。我们这里不聊具体的软件设计,读者请自行参考《A Philosopy of Software Design》

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 设计上的复杂也不可避免,假设下面两个面积接近的矩形的宽度代表接口复杂度,深度代表实现复杂度
* 从用户体验讲,A好过B。代价却是更复杂的实现。
* _____ ___________________
* | | | |
* | | | |
* | | | |
* | | |___________________|
* | | (B)
* | |
* | |
* |_____|
* (A)
*/

一些基本定义

  1. 软件系统代码复杂度(不是算法复杂度)的简单定义:定性的看,设复杂等级的集合K = {游戏复杂指数集合、通信软件复杂指数集合、操作系统复杂指数集合…},对于软件系统(以后简称系统)的功能数量S,与代码总行数L。有L/S正比与K。这里K是针对不同软件类型的集合,因为游戏和OS的复杂度很可能不同,而用Python和C++开发也会有很大的不同,虽然相对复杂度没有区别。
  2. 系统代码质量好坏的定义:一个功能在可用、可信、可扩展、可靠、效率都完全一致的情况下,代码简洁的实现好过代码冗长的实现。
  3. 关键技术突破与消耗时间呈正相关:一个目标任务,在开发期间不会遇到需求变更时,长时间的开发总不会比短时间开发的质量更差。因为如果用1个单位时间可以完成需求,2个或更长单位时间即便什么都不做也不会在关键技术上更差,最差是保持持平(技术含量与代码优美无关)。

Shit Mountain 定律

SM第一定律

任何一个在空间约束差,项目开发时间约束强的系统,都不可避免的存在部分模块或全局代码为屎山的情况。

说明如下:

  • 空间约束差:不限制行数多少和系统size大小,空间上的限制低。大多数情况下因为资源过剩或者根本无法限制,然而另一方面,过分强调短码会影响可读性,走上另一种极端,具体参考《短码之美》
  • 时间约束强:本来不加班做半年的项目,要求加班做一个月,会让人时常burnout的情况

解释:代码的简洁优雅是需要时间雕琢的,实现目标的时间有限,宝贵的时间通常会优先解决关键问题,而过强的时间约束会导致无法投入更多的时间于代码本身,设计模式滥用,Hardcode,逻辑混乱的情况屡见不鲜。有时候短小精美的代码需要花费的时间会长过不假思索的冗长代码。

SM第二定律

系统的复杂在于问题的引入,假设在某环境下关键性的A问题会导致系统复杂,在另一种环境中不存在A问题。那么另一种环境中一定会有有别于A问题的B问题也会导致系统复杂,总会有难以攻克的问题存在。

解释:假设用没有标准算法库的纯C语言实现从一组大数据中进行增删改查操作,只是完成增删改查的基本操作用很朴素的代码就可以解决。现在如果同时对效率有要求(A问题),那么不可避免的要引入索引,实现方式有很多种,无论红黑树、B树亦或其他的算法数据结构,都比朴素的代码复杂。如果现在换成C++或者Python实现(另一种环境),虽然有现成的容器(A问题不存在了),但是为了解耦合增加代码复用,不可避免的要引入设计模式(B问题),其他情况类似,问题是复杂的。

SM第三定律

复杂是主动且被动蕴含的,人为的设计、代码改进走错了方向、团队协作对齐出现问题等都会引入复杂,有不得不解决的难题时也会引入复杂。

解释:无论想不想,除非做的系统毫无技术挑战,不然主动或被动引入复杂在所难免。

总定律

根据SM三定律,任何有市场存在价值的商业系统,若无专门的时间投入到代码质量本身,屎山存在的几率接近100%。

解释:商业系统的特点是要面对市场竞争,所以开发时间有限,这满足第一定律;如果商业系统可以在市场竞争中存活,那么其必有关键技术支撑,这满足第三定律包含复杂因素;因为有复杂因素,第二定律生效,复杂的问题不会凭空消失,从而带来代码开发上的问题。

另一方面,大多数公司限于人员水平或激烈的市场竞争的制约,都采用战术开发的方式。因为对于用户而言,源代码优美与否是不可见的且对于功能影响小的,毕竟维护和扩展的压力在于开发者。所以代码本身的质量上的投资特征是投入高、产出小,并不经济。这就注定造成大部分商用代码的质量存在问题,影响的方面也很多,特别是生命周期短的商用代码。

结论

市场浮躁往往是屎山堆积的借口,在商用代码中也有类似「VMWare」这样正面的例子。结合我自己的经验,迄今为止我摸过5个项目,很幸运有一个项目是从0开始做的,这就更好的帮助我总结了上述定律,如果碰别人的二手代码带来了很多负面影响的借口成立的情况下,自己从0开始写是不是就能一定写好呢?答案已经在前面给出了。

解决方案

  1. 首位永远是好的设计,可以避免很多问题,脑袋决定屁股,定律3
  2. 工具链的改进,比如一些Web的业务代码,如果在速度上不是特别苛求的话,Py比C++好。但是这也只能优化不能根除问题,定律2
  3. 最奢侈的方案:足够的时间去雕琢,投资于未来,定律1。不太适合竞争激烈的商业项目
  4. 你能想到的任何方案——宏观角度:选择好的市场环境、有优秀决策者、高品质团队的公司;微观角度:选择设计良好、代码整洁的项目等
  5. 没有十全十美,必要时刻该佛就佛

参考

本文的参考

推荐尝试