2015年大热的动画片《动脑特工队》中描述了这么一个场景, 冰棒带领乐乐和忧忧抄近路去乘坐思维列车,所谓的“近路”就是穿过抽象思维的房间,在这个房间里, 他们先是变成了变成3D的块,就像计算机图形学里展示的那样:
然后变成平面的图形
最后只剩下一些线条了 !
真是非常生动的展示了人类做抽象活动的过程。
在软件业,抽象能力的重要性怎么说都不为过,因为软件开发是一个高度复杂的智力活动,程序员经常需要面对、处理异常复杂的业务和逻辑,如果你不具备强大的抽象能力,无法把具体变成概念,进而驾驭概念进行思考, 你就很难降低问题的复杂度,从而陷入泥潭,无法自拔。无论你学会了多么强大的程序语言,你的编程能力也很难有质的提高。
当然抽象不仅仅是软件开发的独有概念,在别的领域可以看到更多。在自然科学领域,抽象的例子更多,开普勒定律和万有引力就是很典型的例子。
在16世纪很多人开始相信哥白尼提出的日心说,但一直搞不清楚围绕太阳的行星到底是怎么运动的,轨道是什么样子,著名天文学家开普勒仔细的研究了他的老师——杰出的观测家——第谷留下的大量天文观测数据以后, 提炼出了著名的开普勒三定律, 第一次给出了天体运行规律的解释:
1. 所有行星分别是在大小不同的椭圆轨道上运行
2. 在同样的时间里行星向径在轨道平面上所扫过的面积相等
3. 行星公转周期的平方与它同太阳距离的立方成正比
开普勒三定律从大量的数据中提炼出数学规律, 无疑是非常伟大的发现和抽象, 但这不是最终本质,当然也不是最终的抽象。
行星运动的本质是万有引力定律。
相比于开普勒定律,天才的牛顿所做的抽象向前迈进了一大步,万有引力几乎覆盖了所有大质量物体之间互相吸引和运动的规律, 即简单又优美, 配合牛顿(和莱布尼茨)发明的微积分,可以很容易推导开普勒定律。
如果再加上牛顿力学三定律,尤其是F=ma,整个经典物理学的架子就建起来了,后人所有的工作只是在这座大厦上进行一些装修工作,直到爱因斯坦相对论的出现,才建立一座更宏伟的大厦。
据说爱因斯坦在评价一个研究时,会用美和丑来作为判断标准,有人拿研究成果让爱因斯坦看, 爱因斯坦不说成果的好与坏,反而说“这东西多丑陋啊”, “这东西真漂亮”。
其实一个抽象的东西形式优美,结构简单,很有可能是正确的,很可能抓住了事物的本质。相反如果连形式都丑陋不堪,十有八九不是好的成果。 以此作为标准,万有引力定律无疑是漂亮的,正确的,当然爱因斯坦的E=mc2更加漂亮和简单。
抽象的例子在软件业更是数不胜数:
文件是对I/O的抽象;虚拟存储器是对物理存储器的抽象;进程是对一个正在运行的程序的抽象;我们再增加一个新的抽象:虚拟机, 他提供了对整个计算机(包括操作系统,处理器,程序)的抽象。
Andorid把一个移动应用程序抽象成Activity , Intent, Service, Provider......稍微注意一下就会发现:抽象层次越高,接口的语意就越模糊,适用的范围就越广,到最后就会变成数学模型或者概念。
数学模型和算法
我认为把纷杂的事物抽象到数学层面是最高的抽象,也许会有人会说哲学层面才是:-) ,但到数学层面已经非常难了。尤其是重大的科学发现,身后必然有数学的影子。
牛顿当年为了描述天体的轨道和运动,特别创立了新的数学表示: 微积分。麦克斯韦使用一组方程对电场和磁场行为进行描述。当年爱因斯坦脑海中已经有了广义相对论,但苦于找不到合适的数学形式来描述,他特别花了几年的时间来学习非欧几何和张量分析,最后才得以成功。海森堡用矩阵理论来解释量子力学。。。程序员在开发过程中, 也许能把一个实际的业务问题抽象成数学模型,或者抽象成特定的算法,这样会让程序实现变得非常简单和有趣。
我在之前的公司有幸遇到过一次,把针对税务领域的一个Credit, Debit等概念抽象为在一个二维坐标下点的运动, 问题一下子简化了很多,实现简单,并且非常安全可靠。
正交的概念
但是抽象成数学模型和算法通常是可遇而不可求的, 这种情况下,我们需要退而求其次,试图抽象成若干个正交的概念,来降低复杂度。“正交”在数学上指的是线性无关,最常见的例子就是坐标系下的x轴和y轴,对于一个点来讲,它的x值的变化不会影响到y,y值得变化不会影响到x ,即x和y是正交的。
正交的威力在于互不影响,扩展方便,单用一个坐标轴可以表示一个直线上的所有的点, 再加一个y轴就能表示平面上的所有的点, 再加一个z轴,3维空间中的所有点都能表示出来了!
我们人类的大脑在思考问题的时候是有容量限制的, 难以同时驾驭太多复杂的概念, 如果我们的软件系统也能做成x,y,z坐标这样,就带来了无与伦比的好处,你在处理x轴相关的事情时,不用考虑其他的y和z相关的东西,因为你知道他们不会受到影响, 这样问题的复杂度就从3维一下子下降到1维!更容易把握了。如果单单x 轴仍然很复杂,你要做的就是再次分解成更小的概念,保证正交即可。
接口
如果你说了,我的整个系统还没法抽象成正交的概念, 那只好再退一步,在局部使用接口。
在著名的《设计模式》一书中,其实在反复强调一点: 发现变化并且封装变化,针对接口编程而不是实现编程。 很多人看书是只关注具体的模式,而忽略了模式的本质目的。
我们在开发的过程中要保持一种敏锐的感觉,发现可能的变化并且封装起来,只提供一个精心定义的接口让外界调用。这样你在接口后面所做的任何变化,外边就不受影响了。
例如在JDK中Iterator就是一个很好的抽象, 它将集合本身和集合的遍历分开。 Stream抽象也不错,封装了对文件和网络操作,只是使用起来稍显麻烦。其实 一组定义良好的接口一定是正交的,不然的话接口之间的依赖就会让实现非常麻烦。
总结
说到底,软件设计和开发就是把现实中的问题映射成计算机的语言实现,但现实问题太复杂,细节太多,而且在不断的变化过程中,一般人很难同时对这么的细节进行思考 ,这时候就需要抽象。
我们只有从纷繁复杂的现象中抽取事物的本质,从具体事物提炼出正交的概念,才能驾驭这些概念,才能在一个低复杂度的世界中进行思考。