9

SICP 中有意思的东西(二):抽象(上篇)

 3 years ago
source link: https://zhuanlan.zhihu.com/p/27377576
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

SICP 中有意思的东西(二):抽象(上篇)

编程话题下的优秀回答者

上一篇文章SICP 中有意思的东西(一):SICP 介绍 - 知乎专栏里的介绍了 SICP,评论区出现的大神对 SICP 的一句话评价非常到位。

brambles: 这书就是把很多我们早已会用的东西真正的理论化和体系化,仅凭这就能让自己的水平有质的飞跃

其实如同 brambles 所说的那样,SICP 里写的东西,对于有个两年以上编程经验的人来说,大部分内容是早已接触到的,比如我专栏里的第一篇文章编程的几个思想 - 知乎专栏,里面就封装、解耦、复用进行了一系列阐述和思考,而 SICP 中的“高阶函数”、“抽象屏蔽”、“数据抽象”、“接口”、“模块化”,其实也蕴含着“封装、解耦、复用”的思想,但毫无疑问 SICP 更为理论化和体系化(默默膜拜一下 brambles 大神),将这些思想变成看得见摸得着的方法论,也就是书中序所写到的“掌握控制大型系统中的复杂性的主要技术”。

ps:将特定领域知识理论化、体系化其实是一个大手笔的表现,我认为这其实是我们程序员进阶所需追求的一个目标。

SICP 第一章叫做“构造过程抽象”,第二章叫做“构造数据抽象”,其实这俩都是在讲抽象,只是关注点不同,可以说抽象是计算机科学发展中最为重要的一个基本方法。

构造过程抽象

构造一个复杂程序实际上就是一步步地创建出越来越复杂的计算性对象。

这句话其实在书中重复过多次。这里的“越来越复杂的计算性对象”是什么意思呢?

输出 = f(输入),这个时候 f 其实就是我们的所有代码, f 将会构造出我们需要的输出,这里的输出其实就是“越来越复杂的计算性对象”,比如网页的 view 层、搜索引擎的结果列表、机器学习的结果等等。

那么构造一个复杂程序的最大难点是啥?

就是编写有着无数个中间过程的 f,因为构造 “越来越复杂的计算性对象” 是有着无数中间过程的,不是像运行程序那样(给个输入返回一个输出)一蹴而就的,所以最大难点的落脚点就是过程,如何更优的实现“过程”是关键所在。

过程又是什么呢,过程是从表达式到函数到模块到系统的,下一篇文章 我将把 SICP 中对过程的抽象、构造数据抽象的方法进行一些介绍和思考。

本文较短,附送相关旧文《编程的几个思想》

编程的几个思想

构建 100 行以内的程序或许可以想到啥写啥,但是到了 1000 行,我们就需要多个文件来管理和组织代码。到了现代,大多商业项目的代码,都是几万甚至几十万级别,

封装

编程中,我们会遇到各种各样的复杂数据和情况,如果对此不作为,复杂度会随着代码、项目的功能的增加直线上升到无法管理。就好比一堆杂乱的事物,乱摆乱放,最后如同垃圾堆一样。

我们需要使用 封装 的思想,将复杂的事物变得 “简单”。就像网购的快递一样,快递小哥用长方体纸盒把你买的东西装了起来 ,成千上万的盒子在城市里穿梭,不需要关心你买的东西的大小、重量、类型、收货人、收货地址、手机号,只用关心纸盒的类型和快递单,甚至在很多地方已经有了智能、自动化的物流系统。

从上面我们可以知道,封装的好处

  1. 可以**让你不用关心任何你不需要关注的细节。
  2. 使系统井井有条。

当我们敲代码的时候,好的封装实际上是可以化腐朽为神奇的,它可以将杂乱如同垃圾堆一般的成堆代码 变成一个井然有序的系统,当你调用系统时你会感受到美妙——你不用关心任何你不需要关注的细节。

但是 “将杂乱如同垃圾堆一般的成堆代码 变成一个井然有序的系统”,并不是只靠封装就可以达到,当杂乱的代码被我们封装成 一个一个的模块后,相比原来的杂乱的代码,有一定的提升,但是,假如这些 代码 互相之间的调用和依赖混乱,那只是将 杂乱的代码 变成 杂乱的模块,当代码、功能日益增加,整个系统也会趋近于垃圾堆的样子。

这时我们就需要解耦。

解耦

我们需要将模块之间的调用、依赖关系用更优雅的方式定义,甚至我们需要通过拆分、合并模块来找到更优雅的模块间调用、依赖关系,其实重构有的时候也就是一个打散重装的过程,以获得更好的 模块间调用、依赖关系,以达到解耦的效果。例如我们经常将一个长达五百甚至上千行的代码,拆成上十个文件(比如@7sDream的 zhihu-py3 的第一次重构,想当年我还见过那个整个项目就一个 一千多行的 .py 文件233),其实这就是一个拆分重组文件的过程。

在现代项目中,仅仅是简单的通过拆分重组文件解耦也不够了,当模块众多到快爆炸的时候。我们需要使用系统这种级别的封装,比如框架,比如架构模式,有的时候,增加中间层也常常是我们解耦的有效手段之一。对于 web 开发者,非常值得一说的就是通过 http 协议发送 ajax 请求,它是传统前后端分离的界限,前后端分离里 前后端交互的方式 是 前端系统通过 ajax 请求 从后端系统的 api 接口获取数据。整体上来看,前后端交互的方式 就有解耦和封装的意思,并实实在在提高了复杂应用的质量(可维护性、可变性),对于开发人员的开发效率也提高不少。如果不相信,你可以想想你大学写的 c 语言版聊天程序就知道了,总之那些就是把前后端代码彻底糅合在了一起的代码,甚至都不分前后端,编写、维护那些东西可真是灾难。就如同之前是揉在同一个文件的代码,现在就是糅合在同一个系统里。就好比现代 GUI 程序 都有 MVC 分层,这些(简单的)架构模式既是抽象也是解耦的体现。复杂的架构模式的初衷其实也是与此相同,比如现在流行的 MVVM、react + redux、基于 node 的前后端分离如何评价淘宝 UED 的 Midway Framework 前后端分离? - 互联网 - 知乎等等。

解耦的好处:

  1. 高效开发。
  2. 大大提高代码的可维护性、可变性。

复用

DRY 原则:don’t repeat yourself。

我们经常会将 一些被经常使用的常量 提取出来,这么做的好处一个是避免重复,另一个就是防止 以后常量改动的时候,我们需要将 几处甚至十几处 分散到各个文件的常量改变,这绝对是灾难性的,你要知道少改一处或者多改都将引入新的 bug,而这又是多么容易就会发生啊。

同样是这个原因,我们可以推广到 代码块、函数、模块甚至系统上。

这也是 don’t repeat yourself。 DRY 原则的一个重要原因。

其次对于代码规模更大的 复用,还有 高效开发的显著优点,从 qt、react-native、weex 等层出不穷的跨平台轮子可以看出,跨平台复用从来是我们程序员最热衷的事之一。

复用的好处:

  1. 保证了一致性、正确性,并且易于修改
  2. 提高了开发效率

从写好函数开始

函数是封装、解耦、复用的最小单元,提高封装、解耦、复用的能力,先从写好函数开始,千里之行始于足下。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK