抽象: 程序员必备的能力
抽象指的是从纷繁复杂的事物中提炼本质的过程,是一个具体到概念的过程, 例如苹果、香蕉、生梨、葡萄、桃子等,它们共同的特性就是水果。得出水果概念的过程,就是一个抽象的过程。
在软件业,抽象能力的重要性怎么说都不为过,因为软件开发是一个高度复杂的智力活动,程序员经常需要面对、处理异常复杂的业务和逻辑,如果你不具备强大的抽象能力,无法把具体变成概念,进而驾驭概念进行思考, 你就很难降低问题的复杂度,从而陷入泥潭,无法自拔。 无论你学会了多么强大的程序语言,你的编程能力也很难有质的提高。
当然抽象不仅仅是软件开发的独有概念,在别的领域可以看到更多,例如帝国经常提的“三. 个. 代. 表”,“和. 谐”(当然现在已经变成贬义词了),“中. 国. 梦” ,就是把 把执政理念和民众(或者利益集团)的诉求进行抽象,虽然实施的不怎么样。
在自然科学领域,抽象的例子更多,开普勒定律和万有引力就是很典型的例子。
在 16世纪很多人开始相信哥白尼提出的日心说,但一直搞不清楚围绕太阳的行星到底是怎么运动的,轨道是什么样子,著名天文学家开普勒仔细的研究了他的老师 --杰出的观测家--第谷留下的大量天文观测数据以后, 提炼出了著名的开普勒三定律, 第一次给出了天体运行规律的解释:
1. 所有行星分别是在大小不同的椭圆轨道上运行
2. 在同样的时间里行星向径在轨道平面上所扫过的面积相等
3. 行星公转周期的平方与它同太阳距离的立方成正比
下图展示了第一定律和第二定律
开普勒三定律从大量的数据中提炼出数学规律, 无疑是非常伟大的发现和抽象, 但这不是最终本质,当然也不是最终的抽象。
行星运动的本质是万有引力定律。
相比于开普勒定律,天才的牛顿所做的抽象向前迈进了一大步,万有引力几乎覆盖了所有大质量物体之间互相吸引和运动的规律, 即简单又优美, 配合牛顿(和莱布尼茨)发明的微积分,可以很容易推导开普勒定律
如果再加上牛顿力学三定律,尤其是F=ma , 整个经典物理学的架子就建起来了,后人所有的工作只是在这座大厦上进行一些装修工作,直到爱因斯坦相对论的出现,才建立一座更宏伟的大厦。
插曲:据说爱因斯坦在评价一个研究时,会用美和丑来作为判断标准,有人拿研究成果让爱因斯坦看, 爱因斯坦不说成果的好与坏,反而说“这东西多丑陋啊”, “这东西真漂亮”。
其实一个抽象的东西形式优美,结构简单,很有可能是正确的,很可能抓住了事物的本质。
相反如果连形式都丑陋不堪,十有八九不是好的成果。 以此作为标准,万有引力定律无疑是漂亮的,正确的,当然爱因斯坦的E=mc2 更加漂亮和简单。
抽象的例子在软件业更是数不胜数:
Unix 把所有的设备都抽象成文件的概念,把程序之间的交互用抽象为“管道”。
Andorid 把一个移动应用程序抽象成Activity , Intent, Service,Provider。
Struts 把Web application的行为抽象成MVC 。
......
稍微注意一下就会发现,抽象层次越高,接口的语意就越模糊,适用的范围就越广,到最后就会变成数学模型或者概念:
数学模型和算法
我认为把纷杂的事物抽象到数学层面是最高的抽象,也许会有人会说哲学层面才是:-) ,但到数学层面已经非常难了。尤其是重大的科学发现,身后必然有数学的影子。
牛顿当年为了描述天体的轨道和运动,特别创立了新的数学表示: 微积分
麦克斯韦使用一组方程对电场和磁场行为进行描述
当年爱因斯坦脑海中已经有了广义相对论,但苦于找不到合适的数学形式来藐视,他特别花了几年的时间来学习非欧几何和张量分析,最后才得以成功。
海森堡用矩阵理论来解释量子力学
。。。。
回到软件行业,程序员在开发过程中, 也许能把一个实际的业务问题抽象成数学模型,或者抽象成特定的算法,这样会让程序实现变得非常简单和有趣。
我在之前的公司有幸遇到过一次,把针对税务领域的一个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 抽象也不错,封装了对文件和网络操作,只是使用起来稍显麻烦。
其实 一组定义良好的接口一定是正交的,不然的话接口之间的依赖就会让实现非常麻烦。
总结
说到底,软件设计和开发就是把现实中的问题映射的计算机的语言实现,但现实问题太复杂,细节太多,而且在不断的变化过程中,一般人很难同时对这么的细节进行思考 ,这时候就需要抽象。
我们只有从纷繁复杂的现象中抽取事物的本质,从具体事物提炼出正交的概念,才能驾驭这些概念,才能在一个低复杂度的世界中进行思考。
抽象能力的高低,很大程度上反映了一个程序员的能力的高低。