《SICP》读后感:关于软件本质的一点思考
摘要:软件本身不是目的,人类的需求才是目的,而软件只是达到目的的手段。
软件的本质在于控制复杂性,这个复杂性并非来自于计算机,也并非来自于现实世界,而是来自于人类的思维和知识体系。
软件被使用的广泛性,在于它所满足的人类需求的广泛性。
什么是软件?
从一个简单的例子说起,比如我想计算两个数的和,于是写下这样的python代码
print a + b
但是,这段代码是我的最终目的吗?显然不是,我需要把它在计算机上实际运行,并赋予a和b实际的数值。也许我是在水果,买了5块钱的苹果和10块钱的香蕉,然后计算一共需要支付多少钱。
可以看出,软件是我们为了达到某种目的,而指挥计算机如何去完成这个任务的一系列指令。我们的目的是满足某种需求,而软件只是一个手段,显然完全可以通过其他的手段完成这一任务。
SICP中指出,计算机科学和计算机其实并没有本质联系,而是人类知识的一种组织形式,重在对过程性知识的形式化。如同数学是对说明性知识的形式化。也就是说,计算机科学是关于“如何完成某某任务”的知识的记录工具。
看到这里你也许会觉得我说了半天废话——因为人类所有的生产活动,都是为了满足人类的某种需求。不过这一观点将是本文的基本出发点。接下来我们简单分析一下软件作为一种手段,为什么存在,以及有什么特点。
为什么要有软件?
如果软件是为了满足人类的某些需求,那么软件存在的原因就很直接——因为计算机不能直接满足人类的需求。其中的距离来自于计算机的通用性、单一性和人类需求的特定性、多样性。从原理上讲,计算机只需要0和1两个符号,以及NAND(与非)或者NOR(或非)一种运算就够了。而人类的需求则千差万别。
但是,前面说到,满足需求有多重方式,比如人工计算,或者直接使用硬件搭建电路完成某些功能(而这实际上也是电子领域早期的方式)。那么,人类为什么选择软件这种手段呢?其实很简单,原因和其它的手段一样,不外乎成本。从经济学角度,成本,也就是人类的工作时间。(金钱,可以看做你从别人那里买时间)。经济的一个基本规律是规模效应,生产规模越大,单位成本越低。硬件做的越通用,就越适合大规模生产,就越能降低成本。当然,这一切都要建立在下面的基础之上:
生产软件要比生产硬件的效率高。
软件易于改动,另外有一个重要特点——它一旦被生产出来,就可以几乎零成本的进行重复利用。这是一个能极大提高全人类生产力的方式。试想一下,假设你能写一个软件炒一盘鱼香肉丝,那么全世界的人无论谁想再生产一盘鱼香肉丝,只需要简单的调用一下这个软件。很多软件最初并不是写出来给大家用的,而是为了解决自己的实际问题,比如为了简化工作流程,开发某个产品,验证某个科研思路等等,软件只是个副产品——这么说也许不太恰当,应当说软件是解决问题过程的完整记录。而完成任务之后,软件就可以被别人复用了——全人类只需要一个人造轮子,完全消除了重复劳动,多么高效!
所以下面要谈谈代码复用的问题。
软件复用
好的软件不会消失,而会被移植到新的平台上。
—— 《Linux/Unix设计思想》
复用软件的成本比生产软件要低的多。所以,软件复用是提高生产率的重要手段。如何更好的复用软件,以及如何编写容易复用的软件?要研究这个问题,需要先考虑另一个问题:软件为什么可以复用?
仍然从基本观点出发:软件是满足人类需求的手段。所以,软件的复用,实际上是人类需求的重复性。考虑两个需求,a)计算一组实数的平均数;b)计算高一3班全体同学的2015年数学期末考试平均成绩。显然,需求a是一个更广泛的需求,并且可以预见这一需求在未来会一再的出现。而需求b则是一个很特定的需求,它只会在2015年期末出现一次。如果有两个软件分别满足两个需求,那么满足需求a的软件将会一再的被重用,而满足需求b的软件将会烂在硬盘上。
所以,为什么“好的软件”会被移植到新平台上?因为它们满足的是人类重复需要的需求,无论技术怎么发展,只要这些需求存在,相应的软件就会存在,只是以不同的形式出现。
所以,为了编写能够复用的软件,我们需要关注的是需求。然而,不幸的是,实际中的需求往往是特定的、易变的。比如,公司要在今年双十一推出一项特定的活动,那么单纯为这个活动所编写的软件,在活动结束后就失去了作用。
要解决这个问题,需要对软件进行层次划分。不同层次的软件,通用性和特定性不同。底层的软件单一、通用,重用度高;而上层的软件则特定性高,生命周期短。为了研究这个问题,需要考察另外的问题——复杂性与抽象。
复杂性
软件的首要技术使命是管理复杂度。
——《代码大全》
为什么会出现复杂性这种问题?仍然从基本观点出发——复杂性来自于“计算机仅能提供0、1运算”和人类需求之间的距离。人类的一个需求,可能需要成千上万的0、1运算才能完成,而人脑——很遗憾——只能同时处理7个左右的事物。可以说,软件的复杂性,实际上来自于人类需求的复杂性以及人脑处理能力的局限。
实际上,这种复杂性不仅出现与软件领域,而是人类所有知识和概念体系的共同特点。在SICP中,引用了洛克的一段话,对此有提纲挈领的论述:
心智的活动,除了尽力产生各种简单的认识之外,主要表现在如下三个方面:
1)将若干简单认识组合为一个复合认识,由此产生出各种复杂的认识。
2)将两个认识放在一起对照,不管它们如何简单或者复杂,在这样做时并不将它们合而为一。由此得到有关
它们的相互关系的认识。
3)将有关认识与那些在实际中和它们同在的所有其它认识隔离开,这就是抽象,所有具有普遍性的认识都是这样得到的。
—— John Locke 1690
软件开发的过程,就是将简单元素组合为一个复杂元素,再将复杂元素抽象为简单元素,这样一个不断“组合->抽象->组合->抽象 ……”的迭代过程。
抽象
基于上面的讨论可以看出,软件开发过程中的抽象,本质上是人脑中概念的抽象。这种概念的结构映射到代码上,就成为软件。那么,怎么样是一个好的抽象?好的抽象应该在错综复杂的事物中分解出不变的部分,将它和易变动的部分隔离开。而软件的变动,则取决于人类需求的变动。也就是说,一个好的抽象,也就是一个好的概念,应当反映出人类需求中稳定不变的部分。这样的软件,就会在空间和时间上达到更广泛的复用。
无论过程式、面向对象还是函数式编程,都是提供了一种看待现实世界、进行抽象的方法。如果我们明白了软件结构设计的本质在于为问题设计一个良好的抽象概念体系,那么这些方法之间其实并无本质区别,只是各自具有优缺点的具体的抽象办法而已。
下面我们来看一下计算机科学领域所采用的一些最基础的抽象。
硬件层面
为什么硬件也算抽象呢?因为我们讨论的起点是布尔代数,这是现代电子计算机的数学基础。硬件层面对0、1运算进行了第一层抽象,即一系列的CPU指令,将原本只能进行简单“与、或、非”运算的概念,抽象为提供一系列字节、整数、浮点数、地址等概念。
计算机语言
计算机语言是在硬件层之上的第一层抽象,而本身又分为不同的层次。计算机语言是一种概念抽象方式,所以它是为人设计的,而不是为计算机设计的——因为,如果要指挥计算机做事情,我们只需要“0”、“1”两个符号就够了。计算机语言将硬件提供的功能抽象为若干数据类型的运算、流控制结构、函数等概念,以组成更复杂的软件。可以说,编译器是最大的一个软件复用的例子。然而,这儿不妨思考一个问题:在这个抽象的过程中,是否丢失了什么?不同语言的抽象方式是不一样的,由此所能提供的功能也是有所差别。
操作系统
操作系统是又一种抽象方式,它把一些常用的操作封装为系统调用,这些操作是软件开发中大量的重复性的需求。
各种软件包、框架等等
软件包、框架等不是计算机语言的一部分,而是使用计算机语言编写的软件模块。然而从抽象的角度来说,使用语言构建的模块,和语言本身提供的特性,并无本质区别。同一种数据结构,在某种语言里属于语言本身的一部分,而在另一种语言里则需要使用软件包。然而无论那种情况,并没有本质区别。
业务代码
业务代码是最终完成现实任务的代码,它们最具体、最不具有一般性,所以过时最快。大部分公司的硬盘里沉睡着这类代码。
再论软件复用
越通用的软件,可复用性越强,但是不能直接用来解决实际问题;而满足特定需求的软件,又不具有通用性,如果没有将其中一般性的模块抽象出来,那么将很难复用。既然每个人有自己特定的需求,而软件开发者不可能为每个人开发一套软件,这就要求任何软件都要提供“组合-抽象”的能力,供软件使用者对满足自己的特定需求的功能进行抽象——这种功能可以叫个性化,或者二次开发,或者插件等等。下面举几个具体例子:
linux系统提供了一系列
ls
,grep
等工具,它们是通用的。我现在有个需求,检查某服务的log中是否有error,如果有则向某个特定邮箱发送邮件。当然我可以依次手动运行这些通用工具,但是更好的方法显然是写一个shell脚本,并命名为check_log_err.sh
。这样每次只要运行这一个命令就够了。这就是一种抽象。编辑word文档时,有个特殊需求:将每段的第一个字的字体增大一号、加粗并设置为红色背景。显然我也可以每段每段的进行操作。更简单的方法是录制一个宏,然后在每段上回放这个宏。宏提供了一种抽象手段,将“第一个字字体增大一号、加粗并设置为红色背景”这几个操作抽象为一个操作。从这里大家大概已经能看出,从抽象的角度上讲,宏和脚本没有本质区别,都是实现抽象的手段。实际上,word的宏也是以VBA代码形式保存的。只是对于很多非计算机专业用户,写代码是不方便的,而宏用起来比较直观。
用浏览器下载文件的时候,它每次都会询问存放位置。我想把它放在某个download目录下面,于是设置了一下下载路径,这样就不需要每次设置存放位置了。这也是一种抽象:通过配置文件,将通用的“下载”、“存盘”两个功能组合起来,并提供具体参数,来满足我的特定需求。
还有各种插件,比如chrome、sublime text、foobar等,提供了二次开发的能力,这样用户可以针对自己的特定需求进行开发。从这个意义上说,软件的二次开发功能做的越好,生命力就越强。
从抽象的角度讲,任何软件都应当具有二次开发的能力:使用者将软件提供的若干个相对通用的功能,组合、抽象为一个功能,来满足自己的特定需求。这种组合、抽象的功能越强大、越方便,软件的生命力就越强。
小结
本文的主要观点总结如下:软件不是目的,而是手段。人的需求才是目的。在布尔代数和人的需求之间,存在着巨大的距离;而人脑处理能力是有限的,这就产生了复杂性。控制复杂性是一个“组合->抽象”的不断迭代过程。良好的抽象应当满足人类普遍的、长期的需求,这样的抽象以及相应的软件将实现更大程度上的复用。
扩展:关于人工智能
在这个基础上,笔者思考一个问题:未来是否会出现人类理解不了的人工智能?沿着本文的思路,不妨这样考虑:人工智能建立在计算机的基础上,那么它就不能超越布尔代数;而人工智能也是一种软件,它的目的是满足人类的某种需求。但是,中间的过程可能是不一样的,也就是说,人工智能可能会采取和人类不同的抽象方式,或者它根本就不需要抽象。大量的中间层次,可能是人脑不能理解的。但是至少从基础的层面上,人工智能只要还构建在图灵机之上,那么它的基础——0、1运算至少还是能被人脑理解的。
《SICP》读后感:关于软件本质的一点思考的更多相关文章
- 关于《加密与解密》的读后感----对dump脱壳的一点思考
偶然翻了一下手机日历,原来今天是夏至啊,时间过的真快.ISCC的比赛已经持续了2个多月了,我也跟着比赛的那些题目学了2个月.......虽然过程很辛苦,但感觉还是很幸运的,能在大三的时候遇到ISCC, ...
- 对dump脱壳的一点思考
对dump脱壳的一点思考 偶然翻了一下手机日历,原来今天是夏至啊,时间过的真快.ISCC的比赛已经持续了2个多月了,我也跟着比赛的那些题目学了2个月.......虽然过程很辛苦,但感觉还是很幸运的,能 ...
- 关于linux kernel slab内存管理的一点思考
linux kernel 内存管理是个很大的话题,这里记录一点个人关于slab模块的一点思考总结. 有些书把slab介绍成高速缓存,这会让人和cache,特别是cpu cache混淆,造成误解.sla ...
- 关于java异常的一点思考
关于异常的一点思考 异常生命周期 异常的来源 所有的异常都是抛出来的 有底层api抛出的 有自定义抛出的 异常的处理 1, 运行时异常 不做任何处理仍可编译通过 不建议捕获(不建议用异常来做流程控制, ...
- MSSQL显错注入爆数字型数据的一点思考
Title:MSSQL显错注入爆数字型数据的一点思考 --2011-02-22 15:23 MSSQL+ASP 最近在弄个站点,密码是纯数字的,convert(int,())转换出来不报错,也不知道其 ...
- 关于html页面元素语义化的一点思考
这几天在看招聘公告前端工程师的要求基本都附带了html语义化的要求,所以稍微关注了下这方面的知识.对于其中的一点就是要求页面元素在去除css样式之后还能有良好的布局引发了我一点思考.作为前端刚入门的我 ...
- c#Winform程序调用app.config文件配置数据库连接字符串 SQL Server文章目录 浅谈SQL Server中统计对于查询的影响 有关索引的DMV SQL Server中的执行引擎入门 【译】表变量和临时表的比较 对于表列数据类型选择的一点思考 SQL Server复制入门(一)----复制简介 操作系统中的进程与线程
c#Winform程序调用app.config文件配置数据库连接字符串 你新建winform项目的时候,会有一个app.config的配置文件,写在里面的<connectionStrings n ...
- 基于CAS分析对ABA问题的一点思考
基于CAS分析对ABA问题的一点思考 什么是CAS? 背景 synchronized加锁消耗太大 volatile只保证可见性,不保证原子性 基础 用CPU提供的特殊指令,可以: 自动更新共享数据; ...
- 【翻译】全球用尽IPv4的一点思考
作者:Dimple 公众号:奔跑吧攻城狮 简介:专属于Java和Android开发,和你聊聊职场话题,一同展望未来 作为小小号主的我表示很无力啊,这几天,天天都是热点.前有网易员工勇敢发声维护自己的利 ...
随机推荐
- bzoj2424 [HAOI2010]订货
模拟一下仓库里面存储物品的价格情况即可,如果当前物品大于仓库里面物品那么就替换一下仓库里的物品,然后订货直接从仓库里先取,仓库里不够则直接购买,每次做完后记得买当前物品填补一下仓库直至仓库填满,当然这 ...
- 最长公共上升子序列(LCIS)
最长公共上升子序列慕名而知是两个字符串a,b的最长公共递增序列,不一定非得是连续的.刚开始看到的时候想的是先用求最长公共子序列,然后再从其中找到最长递增子序列,可是仔细想一想觉得这样有点不妥,然后从网 ...
- -XX:-PrintClassHistogram 按下Ctrl+Break后,打印类的信息
-XX:+PrintClassHistogram –按下Ctrl+Break后,打印类的信息: num #instances #bytes class name ------ ...
- Linq To Sqlite 一一二二
说在前头 之所以写下这些文字,主要是因为使用LINQ的同志们都觉它的美好(至于有多美好,各位心里知道,我就不在描述了,如果你是你还不了解LINQ,园子里有大把的文章),微软老哥只提供了自家的SQLSe ...
- Windows10 如何删掉内置的 skype ?
打开开始菜单,输入“PowerShell”并回车: 运行“Get-AppxPackage -User username”命令( username 请替换成当前实际用户名),此时会显示所有已安装的应用程 ...
- JQuery基础一
1.在浏览器点击F12,调出源码设置端点F11进行调试 2.要操作DOM对象,首先要把DOM对象封装成juery对象: jQuery(document).ready(function () { ale ...
- 使用 jsPlumb 绘制拓扑图 —— 异步加载与绘制的实现
本文实现的方法可以边异步加载数据边绘制拓扑图. 有若干点需要说明一下: 1. 一次性获取所有数据并绘制拓扑图, 请参见文章: <使用 JsPlumb 绘制拓扑图的通用方法> ; 本文实现 ...
- java中快速排序的理解以及实例
所谓的快速排序的思想就是,首先把数组的第一个数拿出来做为一个key,在前后分别设置一个i,j做为标识,然后拿这个key对这个数组从后面往前遍历,及j--,直到找到第一个小于这个key的那个数,然后交换 ...
- 预编译命令 #if DEBUG
在控制台程序根据预编译命令: http://www.askapache.com/windows/advanced-batch-scripting.html namespace SXGYCarTrans ...
- WPF里的报警闪烁效果
<esri:MarkerSymbol x:Key="FlashMarkerSymbol" OffsetX="41" OffsetY="41&qu ...