编程核心能力之抽象

用户头像
顿晓
关注
发布于: 2020 年 07 月 12 日
编程核心能力之抽象

网上流传过这样一个段子『假如要消灭火星,消灭土星,好的程序员不会分别写两个函数去实现,而是会实现一个消灭行星的函数,然后把火星,土星当参数传进去』。



这个例子,我一直印象深刻,促使我逐渐养成了一个习惯:每当有新的问题,就会多想一想能不能增加抽象。



今天我们在这个例子上扩展一下:



大家思考一下,需求是消灭太阳系的行星,这里功能的主体是消灭,客体是具体的行星。由于同在太阳系,这些行星差异不会太大,对于消灭这个功能来说,可能流程都一样,比如都是发射一颗炮弹的事。



所以,我们拿到问题后,先确定解决问题的主体,然后给出正确的解决方法,在这就是发射一颗炮弹,将其封装成一个函数即可。



接下来就是如何处理不同客体了,可以发现只需知道客体的坐标就满足主体的要求了。



这里有一点比较重要,就是:客体的属性随解决方案的主体而定。一个常见的误区是:在解决方案还没确定的时候,就先考虑客体的属性,然后这些属性先入为主,最终影响了解决方案的选择。



个星球大战的任务只是消灭2个行星,这次任务升级了,要消灭太阳系所有行星。



上个任务中,我们把「消灭」当作主体,「行星」当作客体,有没有感觉这次任务的主客关系有点不一样了?所有行星好像更强势,想要做主体。



为什么会产生这个感觉呢?



我们仔细对比分析下2次任务的过程:



第一次任务,消灭火星,然后消灭土星,可以发现消灭这个动作是重复的,火星和土星对于消灭来说是变化的。



再看这次的任务,还是一个一个去消灭,就觉得有点啰嗦,究其原因,应该是深入到所有这个集合的内部细节了,细节可能会变化,导致依赖不稳定,所以希望能有更简洁且稳定的表达。



但在原来的主客关系下,只能那样啰嗦的表达,这时就需要考虑新的主客关系。



为了凸显消灭所有这个意图,需要新的角色做主体,这里就是所有行星的集合,消灭做客体,主客之间的关系,即主体所有行星调用客体消灭,在大部分编程语言中叫 map。



map 是个很重要的概念,集合带 map 为什么能成为主体呢?集合有自己的一套逻辑,如遍历。所以像遍历这样的逻辑可以做主体,消灭的逻辑作为扩展,和遍历进行组合。



这里有2个很重要的概念,如果不能正确理解的话,再往高维抽象,几乎就看不懂了。可以说是进阶的必经之路。



它们是计算的表达和执行。



如函数的定义就是表达,包含函数头,也就是签名部分和函数体,也就是实现部分。



一种常见的执行方式是在函数名后加括号,带上数据做参数。这是普通函数的执行方式,参数和函数体都确定后,每次执行的结果是一样的。



当把一个函数当作参数传给另一个函数时,是不会执行的,这也是支持函数式编程所必要的。



传入的函数相当于扩展了原有逻辑,对于调用者来说就是把2段逻辑组合成了新的逻辑。



在上面的例子中,遍历所有行星和消灭组合成了新的逻辑,其实,这段新逻辑才是主体,客体是集合本身。



如果需要拿到集合遍历执行一段逻辑后的结果,就得通过map组合并执行。

所以,map 是执行逻辑的抽象,对于集合来说就是遍历。



这次的主题是抽象能力:



我们用第一个例子:消灭行星,来提出需求的主体和客体概念。



然后用第二个例子:消灭太阳系所有行星,来思考主体的变化趋势。并详细分析了新主体产生的过程,以及产生的基础:函数表达和执行的分离。



为什么要先费时搞清楚需求的主体和客体呢?



因为这决定了你所提供的功能如何实现,在该实现的基础上谈抽象才有意义。



这也可以理解为拥有技术思维,直到解决一个问题用什么技术合适。



算是完成了从需求到技术抽象的第一步:确定解决方案中的主体和客体。这一步如果弄错了,一般是搞反了,会直接影响后面代码的表达方式,甚至会导致不能抽象至最简。



接下来的抽象就会比较顺利:先确定主体的执行逻辑。



如个体的话,一般抽象一个函数即可,调用函数来执行。

容器的话,如集合,有map, lift, flat, reduce等。

还有流,跟容器差不多,想象成无限容器即可。



然后执行逻辑可以通过组合其他函数的方式实现最终的功能。



抽象的流程大体如此,只是在不同语言中,实现方式有差异,要处理的细节不同。编程日课后续会逐一剖析,欢迎关注。



刚才梳理了需求转化为代码需要三个抽象步骤,简称抽象三部曲。



抽象的目的是为了写出方便组合的代码。



在动态语言中,符号可以表示任意数据,所以可以把精力都花在如何函数的组合上。



另外,动态语言的函数式表达也更简洁,能轻松玩转高阶函数,所以推荐初学编程的先学一门动态语言,如 JavaScript。



静态语言由于要在编译期知道数据的类型,如在定义函数时,就需要和类型绑定在一起,这限制了函数的抽象度。



为了解决这个问题,静态语言大多才有泛型这种零成本抽象的方式,以增加少量定义的方式使函数能和动态语言媲美。



泛型解决了数据类型的差异,数据存储结构的差异则通过容器的迭代器来解决。

发布于: 2020 年 07 月 12 日 阅读数: 32
用户头像

顿晓

关注

因观黑白愕然悟,顿晓三百六十路。 2017.10.17 加入

视频号「编程日课」 一个不爱编程的程序员, 一个用软件来解决问题的工程师, 一个有匠心的手艺人。

评论

发布
暂无评论
编程核心能力之抽象