写代码不难,难的是让代码在十年后依然能被人看懂、被机器跑通。
如果你问一个资深程序员:软件开发中最难的是什么?他大概率不会回答"算法"或"高并发",而是会叹一口气,说出两个字:复杂性。
编程的本质,从来不是教会计算机做什么。计算机很听话,你给它什么指令它就执行什么。真正的挑战在于:人类的思维是有限的,而我们要构建的系统,其规模与混乱程度,往往远远超出一个大脑能承载的极限。
于是,软件工程的全部智慧,归根结底可以用一句话概括:如何对抗复杂性。 这是一场没有终点的战争,而我们手中的武器,是分层、抽象、命名与克制。
一、复杂性的两种面孔:本质的与偶然的
在开始讨论如何对抗之前,我们得先弄清楚敌人长什么样。
计算机科学家弗雷德里克·布鲁克斯(Fred Brooks)在他的经典著作《人月神话》中,提出了一个影响深远的区分:本质复杂性 与偶然复杂性。
本质复杂性,是问题本身自带的难度。你要做一个火箭发射控制系统,它的复杂是因为航天动力学本身就复杂;你要做一个抖音那样的推荐算法,它的复杂是因为人类兴趣的预测本身就难。这种复杂无法消除,只能被管理和应对。它是软件开发的"硬骨头",也是我们拿高薪的原因。
偶然复杂性,则是我们在解决问题的过程中,自己给自己添的麻烦。
- 因为你用了错误的框架,导致配置文件写了一千行;
- 因为当初偷懒没有做模块划分,导致改一个功能得改二十个文件;
- 因为命名太随意,导致你自己一个月后回来看代码,都忘了这个变量是干嘛的。
这些,都是"自作自受"的复杂性。更可怕的是,软件行业的绝大多数项目,最终倒闭或重构的原因,不是因为"本质复杂性"太难攻克,而是被"偶然复杂性"活活拖垮的。
所以,优秀的程序员与平庸的程序员,最大的区别往往在于:前者懂得区分这两种复杂性,并穷尽一切手段消灭偶然的,优雅地管理本质的。 而后者,则在无意中把两种复杂性混为一谈,最终让项目陷入泥潭。
二、第一件武器:分而治之
既然我们的大脑一次只能处理有限的几件事,那就把庞大的系统切碎。
"分而治之"是所有工程学科的基石,在软件领域尤其如此。它的具体表现形式有很多:模块化、分层架构、微服务......名字在变,内核不变:把一个复杂的大问题,拆解成若干个小问题,让每个小问题足够简单,简单到可以被单独理解、单独开发、单独测试。
想象一下,如果没有分层的概念,你的业务逻辑会直接和SQL语句、网络协议、文件读写搅在一起。那将是一场灾难。你改一行代码,可能要担心数据库连接会不会断,担心缓存会不会失效------因为所有东西都耦合在一起。
而分层的魅力在于:每一层都假装其他层不存在。 业务层只关心业务规则,它调用数据层时,只是说"给我用户123的信息",它不关心数据层是从MySQL查的还是从Redis取的。数据层也不关心业务层是谁在调用它。
这种"假装"在计算机科学里有一个专门的词------抽象。抽象就是忽略不相关的细节,只暴露清晰的接口。当你面对一个抽象良好的系统时,你永远只需要关心当前这一层的事情,而不必坠入底层细节的无底深渊。
这便是对抗复杂性的第一法则:分割战场,各个击破。
三、第二件武器:命名与表达
这听起来有点过于朴素了。命名?不就是起个名字吗?有那么重要?
很重要。重要到有程序员开玩笑说:计算机科学只有两大难题------缓存失效、命名,以及 off-by-one 错误。
命名的本质,是用人类的语言为抽象的代码逻辑贴上一张"含义"的标签。代码是写给计算机执行的,但更是写给人看的。你的变量名、函数名、类名,是连接机器逻辑与人类思维的桥梁。桥若断了,谁也过不去。
我们见过太多这样的代码:
python
def do(x, y):
a = x.get("data")
b = a.process()
return b * 1.1